I have IKabaSDK.h
which is a (Objective-C) protocol :
@import Foundation;
@import MobileSdk;
NS_ASSUME_NONNULL_BEGIN
@protocol IKabaSDK <NSObject>
- (BOOL)isStarted:(NSError* _Nullable __autoreleasing * _Nullable)error
__attribute__((swift_error(nonnull_error)))
NS_SWIFT_NAME(isStarted());
I have a (Swift) class KabaSDKThunk
where the said protocol is implemented:
class KabaSDKThunk: NSObject, IKabaSDK {
func isStarted() throws -> Bool {
do {
try sdk.isStarted()
print("thunk getIsStarted")
} catch {
print("thunk getIsStarted throws \(error)")
throw error
}
}
...
}
Xcode gives me these two errors next to my implementation:
- Non-
@objc
methodisStarted()
does not satisfy requirement of@objc
protocolIKabaSDK
- Throwing method cannot be an implementation of an @objc requirement because it returns a value of type
Bool
; returnVoid
or a type that bridges to an Objective-C class
And If I don't add Bool
it complains that KabaSDKThunk
does not conform to protocol IKabaSDK
and suggests to add the Bool
return type, so the errors are mutually exclusive. How do I solve the issue and why do we need Bool
and throw
here?
This question boils down to what you in fact want to achieve. I agree the given behaviour is inconsistent, and moreover, the Swift bridging rules were changed in the past and is likely a subject to change in future, so what you have currently broken, can turn into working sample in foreseeable future. However for now, this error says it all:
In other words, if your Swift method is marked with
throws
keyword (and exposed to Objective-C runtime), it has to return a plain Objective-C type (not magically bridged Swift structures, likeInt
,Bool
,Double
, etc.. it has to be something subclassed fromNSObject
,NSProxy
or other Objective-C entities) OR return nothing. Period. Take it as a rule (at least for now). (P.S. This specific situation indeed looks like an LLVM bug, because the same set of requirements works perfectly fine when applied to a non-protocol Objective-C method (submitted it here, so the community has a chance to review it))Having that said, the proper workaround to this situation depends on your final goal.
"Conventional" failable method
By "conventional" here I mean a contract that Cocoa/Cocoa touch programmer would usually expect. In this scenario a method with the following signature:
Is commonly meant to fail wherever it returns
NO
value (in the world of Objective-C). It's uncommon for Swift to deal with indirect parameters likeNSError **
so in order to keep it consistent such methods are bridged as follows:You can read more about this convention in the About Imported Cocoa Error Parameters documentation.
Failable method with preserved return type with
swift_error(nonnull_error)
attributeIf you want to preserve return type AND the failable signature, you have two options. First is by giving the method
swift_error(nonnull_error)
attribute. However in this case, in order to comply with existing bridging rules, your method has to have a type that "bridges" to an Objective-C class, e.g.NSNumber *
:And here is how you implement such a method in Swift:
Failable method with preserved return type with
swift_error(none)
attributeAnother option to preserve the return type is by disabling Objective-C - Swift error signature conversion at all with use of
swift_error(none)
attribute:In this scenario you can return a scalar type from the failable method, but at the same time you will have to deal with
NSErrorPointer
type: