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?
why do we need
Bool
andthrow
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:
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
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, like Int
, Bool
, Double
, etc.. it has to be something subclassed from NSObject
, 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.
By "conventional" here I mean a contract that Cocoa/Cocoa touch programmer would usually expect. In this scenario a method with the following signature:
- (BOOL)failableMethodWithError:(NSError **)error;
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 like NSError **
so in order to keep it consistent such methods are bridged as follows:
func failableMethod() throws
You can read more about this convention in the About Imported Cocoa Error Parameters documentation.
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 *
:
- (NSNumber *)failableMethodWithError:(NSError ** _Nullable)error __attribute__((swift_error(nonnull_error)));
And here is how you implement such a method in Swift:
func failableMethod() throws -> NSNumber {
return NSNumber(booleanLiteral: false)
}
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:
- (BOOL)failableMethodWithError:(NSError ** _Nullable)error __attribute__((swift_error(none)));
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:
func failableMethodWithError(_ error: NSErrorPointer) -> Bool {
if (/*error condition */) {
error?.pointee = NSError(domain: TDWErrorDomain, code: TDWErrorDomainErrorCode)
}
return true
}