pbuchheit pbuchheit - 1 month ago 12
Swift Question

Using An Objective-C Function that Returns an Optional or Throw in Swift

I'm working on a swift project that need to interact with an existing objective-c api. I've run into a bit of a roadblock with one of the functions though. In the objective-c header file (OrderItem.h) I have this function definition:

+ (NSString *_Nullable)getOptional:(NSString *_Nonnull)foo error:(NSError *_Nullable *_Nullable)error;


In particular, take notice of the last parameter; because it is an error pointer calls to this method in swift will need to be wrapped in an error hander (do .. catch).

Here is the corresponding .m file:

+ (NSString *)getOptional:(NSString *)foo error:(NSError *__autoreleasing *)error
{
if([foo isEqualToString:@"abc"])
{
return @"abc item";
}
else
{
if([foo isEqualToString:@"xyz"])
{
*error = [[NSError alloc] init];
}
return nil;
}
}


In my swift file I then added this code:

func testGetOptional()
{
do
{
var result:NSString? = try OrderItem.getOptional("abc");
XCTAssertNotNil(result);
result = try OrderItem.getOptional("123");
XCTAssertNil(result);

}
catch let error as NSError
{
XCTFail("OrderItem lookup should not have thrown an error. Error was " + error.localizedDescription);
}

}


Nothing especially complicated; neither of the two calls to getOptional should actually result in an error. When I run that function however, the '123' case is blowing up and causing the test to fail. When I took a closer look, it seems that the bridged version of my objective-c is defining the return type as Nonnull (-> OrderItem) even though I explicitly defined it as Nullable in the objective-c. Even more strange, if I declare this same function without the final 'error' parameter then the bridged version will have the correct return type of Nullable (-> OrderItem?).

Can someone shed some light on what is going on here? More importantly, is there some way to work around this issue?

Answer

In Cocoa there is the error pattern that a method that can fail will have a return value and an indirect error parameter. If it is a method returning a object reference, the pattern is

  • set the reference the indirect error argument points to,

  • return nil

So it looks like this

+(NSString *) getOptional:( NSString *) foo error:(NSError *__autoreleasing *)error
{
   …
   // Error case
   *error = [NSError …];
   return nil;
   …
 }

In Swift errors are translated into a docatch construct. Therefore a return value of nil signaling an error is never used from the Swift point of view, because the execution is caught. Therefore it is non-nullable.

Comments