30

I have some Objective-C legacy code, that declares method like

- (void)doSomethingWithArgument:(ArgType)argument error:(NSError **)error

As written here https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html

Swift automatically translates Objective-C methods that produce errors into methods that throw an error according to Swift’s native error handling functionality.

But in my project described methods are called like this:

object.doSomething(argument: ArgType, error: NSErrorPointer)

Moreover, it throws runtime exception when I try to use them like:

let errorPtr = NSErrorPointer()
object.doSomething(argumentValue, error: errorPtr)

Do I need something more to convert Objective-C "NSError **" methods to Swift "trows" methods?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Anton Tyutin
  • 626
  • 1
  • 5
  • 15

2 Answers2

53

Only Objective-C methods are translated to throwing Swift methods, which do return a BOOL (not lower-cased bool), or a nullable-object. (Tested with Xcode 11.7, and Swift 5 language.)

The reason is that Cocoa methods always use a return value NO or nil to indicate the failure of a method, and not just set an error object. This is documented in Using and Creating Error Objects:

Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.

For example, the Objective-C interface

@interface OClass : NSObject

NS_ASSUME_NONNULL_BEGIN

-(void)doSomethingWithArgument1:(int) x error:(NSError **)error;
-(BOOL)doSomethingWithArgument2:(int) x error:(NSError **)error;
-(NSString *)doSomethingWithArgument3:(int) x error:(NSError **)error;
-(NSString * _Nullable)doSomethingWithArgument4:(int) x error:(NSError **)error;
-(BOOL)doSomething:(NSError **)error;

NS_ASSUME_NONNULL_END

@end

is mapped to Swift as

open class OClass : NSObject {

    open func doSomethingWithArgument1(x: Int32, error: NSErrorPointer)
    open func doSomethingWithArgument2(x: Int32) throws
    open func doSomethingWithArgument3(x: Int32, error: NSErrorPointer) -> String
    open func doSomethingWithArgument4(x: Int32) throws -> String
    open func doSomething() throws
}

If you can change the interface of your method then you should add a boolean return value to indicate success or failure.

Otherwise you would call it from Swift as

var error : NSError?
object.doSomethingWithArgument(argumentValue, error: &error)
if let theError = error {
    print(theError)
}

Remark: At

I found that Clang has an attribute which forces a function to throw an error in Swift:

-(void)doSomethingWithArgument5:(int) x error:(NSError **)error
  __attribute__((swift_error(nonnull_error)));

is mapped to Swift as

public func doSomethingWithArgument5(x: Int32) throws

and seems to work "as expected". However, I could not find any official documentation about this attribute, so it might not be a good idea to rely on it.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks, really nice answer – Anton Tyutin Jan 14 '16 at 14:51
  • 3
    Nice find! I found the documentation for the attributes here: https://github.com/apple/swift-clang/blob/383859a9c4b964af3d127b5cc8abd0a8f11dd164/include/clang/Basic/AttrDocs.td#L1800-L1819 – Dia Kharrat Jun 23 '16 at 10:49
  • Looks like this works for initializers, too: `- (instancetype _Nullable)init:(NSError**)error;`. However, to call it from Swift, I had to pass an empty tuple `()` as an argument. – bugloaf Jan 30 '20 at 18:29
  • Martin, is there a way to translate a closure, either as a property or a parameter? I can't find a working example, but have been trying (to no avail): `typedef BOOL(^Snafu)(NSString*, NSError**);`, with a method of `-(void) doSomethingWithClosure: (Snafu) closure;`, and property of `@property (nonatomic, copy) Snafu _Nullable snafu;` .Thank you. – Chris Conover Sep 16 '20 at 01:06
  • I think this answers my question (and a good test): https://stackoverflow.com/questions/49409191/is-it-possible-that-bridging-header-turns-void-nserror-block-objc-into – Chris Conover Sep 16 '20 at 01:26
4

You need to make your method return a BOOL, to tell the runtime that an error should or should not be thrown. Also you should add __autoreleasing to the error parameter, to make sure ARC doesn't accidentally release the error before you have a chance to use it:

- (BOOL)doSomethingWithArgument:(ArgType)argument error:(NSError * __autoreleasing *)error

You can then call it from Swift like this:

do {
    object.doSomethingWithArgument(someArgument)
} catch let err as NSError {
    print("Error: \(err)")
}
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 2
    `__autoreleasing` is actually assumed by default for an `NSError **error` parameter. Otherwise you are right. – Martin R Jan 14 '16 at 11:02
  • Thanks for the tip... another thing I learned today :) This [SO post](http://stackoverflow.com/a/13590696/1974224) and the [Clang documentation](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#indirect-parameters) confirm what you said :) – Cristik Jan 14 '16 at 11:10