Swift Doesn't Convert Objective-C Nserror** to Throws

Swift doesn't convert Objective-C NSError** to throws

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

  • https://github.com/apple/swift/blob/master/test/Inputs/clang-importer-sdk/usr/include/errors.h

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.

How can I throw an NSError from a Swift class and catch it in an Objective-C class?

The problem is that a Swift Error / Objective-C NSError is not an NSException. You are configured to catch NSExceptions but that is irrelevant.

The way to "catch" an NSError in Objective-C when Swift throws an Error is by indirection with the NSError** parameter, just as it always has been.

NSError* err = nil;
BOOL ok = [self.netServiceManager requestHelloPageAndReturnError:&err];
if (ok) {
// proceed normally
} else {
// you got an error, it is sitting in `err`
}

(Notice how Swift supplies a BOOL result exactly so you can implement the correct pattern.)

How to write swift 3 method with throws statement and return value that can be used in Objective C?

The standard way of reporting success or failure in Objective-C is
to return the boolean value NO or a nil object pointer,
as 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.

So you can return a instance of an object

func doSomething(param: String) throws -> NSNumber

which is translated to

- (NSNumber * _Nullable)doSomethingWithParam:(NSString * _Nonnull)param error:(NSError * _Nullable * _Nullable)error;

and returns nil if an error is thrown, or return a boolean and
pass other values back by reference

func doSomething(param: String, value: UnsafeMutablePointer<Int>) throws

which is mapped to

- (BOOL)doSomethingWithParam:(NSString * _Nonnull)param value:(NSInteger * _Nonnull)value error:(NSError * _Nullable * _Nullable)error;

Is it possible to pass a NULL NSError** to a Swift function that throws?

On WWDC'15 Session 401 (Swift and Objective-C Interoperability) Doug Gregor said:

This means, 'I thought about it, I couldn't come to an answer.' The best thing to do is keep it implicitly unwrapped optional in Swift, keep it null-unspecified here.

So, basically, your null is mapped to ImplicitlyUnwrappedOptional<ErrorType>.None, that pretty much explains the crash.

On the session above they mention NSError ** is assumed to be nullable on both pointers. Apparently they've changed their mind or the mapping is not symmetrical, anyway it looks wrong to me.

Considering that behavior and not being NSError * __nullable * __nonnull I'd say it's a bug, I'd open a radar. If you do please let us know so we can dupe it.

@throws or NSError which one is appropriate in for objective C api kind of methods

Unlike Java, in Objective-C exceptions are just used to handle unrecoverable states. This means that for example, you don't catch EXC_BAD_ACCESS, rather you debug your application and correct the problem.

The Objective-C equivalent of Java exceptions is NEError and the null pointer pattern. For example if a formatter fails to format a string to a number because the string does not represent a number, it returns nil. There is also a method to format the number that takes a NSError double pointer to return the error description.

So to handle errors you typically implement a method like this:

- (id) object : (out NSError**) error
{
BOOL ok= ...
if(!ok)
{
if(error)
*error= ...
return nil;
}
return someObject;
}


Related Topics



Leave a reply



Submit