I want to have a function in Swift that returns a Bool
but that can also throw
if an exception occurs.
For example:
func doSomething(value: Int) throws -> Bool {
if (value > 0) {
return true
} else if (value == 0) {
throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
}
return false
}
This works fine, from Swift, but if I try and use this function from Objective-C, the compiler can't find the method. I know that the throws
requires the Objective-C function signature to change to doSomething:x error:&error
and this works, if I change the return type to Void
-
func doSomething(value: Int) throws -> Void {
if (value == 0) {
throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
} else if (value < 0) {
throw NSError(domain: "SwiftClass", code: -1, userInfo: nil)
}
}
But this has different semantics. In the first example, I only need to deal with the exception (or non-nil NSError
) if there is a problem. With this code I have to catch the exception (or examine the error) and determine if it is a real problem or just the valid, "false" case.
Is it really not possible to use a Swift function with a non-void return that throws in an Objective-C context?
As noted in my comments, I'm unclear as to why this shouldn't work. The documentation doesn't give anything to suggest this shouldn't work, but it also doesn't explicitly state that it should. My reading of the documentation says that it should work.
With that said, we could wrap this in a method that is callable from Objective-C by changing the return type to Void
and using an inout
parameter for the result:
func doSomething(value: Int) throws -> Bool {
if (value > 0) {
return true
} else if (value < 0) {
return false
} else {
throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
}
}
func doSomething(value: Int, inout result: Bool) throws {
do {
result = try doSomething(value)
} catch let error {
// If need be, assign some default value to result.
throw error
}
}
Alternatively, as per your comment, it seems the compiler would be happy if we returned a value that can bridge to an Objective-C class (which apparently doesn't include Bool
), so we could wrap it as such:
@objc func doSomething(value: Int) throws -> NSNumber {
do {
return try doSomething(value)
} catch let error {
throw error
}
}
And in playing around with this, it became clear why this only works when we're returning a value that can map to an Objective-C class.
The compiler will not let you return an optional from a method marked with @objc
and throws
. Why? Because while on the Swift side, we use try
semantics to call the method, the approach is entirely different in Objective-C. nil
is used to indicate failure.
So trying to create a method with this signature in Swift:
@objc func doSomething(value: Int) throws -> NSNumber?
Generates this warning:
Throwing method cannot be marked @objc because it returns a value of optional type 'NSNumber?'; 'nil' indicates failure to Objective-C
In the end though, I do still very much recommend that you write the Swift method as having the signature returning Bool
and write a wrapper method returning NSNumber
so that we still have our Swift method with the most accurate type possible.
Also, if you notice, Bool
can happily automatically box up into NSNumber
. My wrapper method was written as returning type NSNumber
, but the method I'm wrapping returns type Bool
, yet Swift was perfectly happy to return Bool
from a method that is supposed to return NSNumber
. The trouble most likely is that by default, if unspecified, Swift's Bool
translates into Objective-C's BOOL
, which is a non-class.
Objective-C wouldn't be able to distinguish between the three possible cases if your return type is BOOL
. It only has two possible returns from a BOOL
method: YES
or NO
. With Bool
mapped to an NSNumber
, it can return @YES
, @NO
, and nil
.
And if it's me, NSNumber
isn't really type strict enough for me. I might be encouraged to write my own wrapped class for this.
@objc class BooleanObject: NSObject, BooleanType {
let boolValue: Bool
init(_ boolValue: Bool) {
self.boolValue = boolValue
}
}
Note that the BooleanType
protocol means that Swift would still be perfectly happy to use variables of this type as if they were a regular Bool
.
let b = BooleanObject(true)
if b {
// b's boolValue property is true
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With