Point of No Return

I found an interesting (to me) aspect of Swift/Objective-C interactions this week.

Take this Objective-C method:

+ (nullable NSData *)dataWithString:(nullable NSString *)string error:(NSError **)error

It uses the standard Apple pattern of having both a return value and an error. (I left out the error’s nullability annotations for brevity, as Apple always assumes them.)

In theory — and, if I’m remembering correctly, according to Apple guidelines — first, you’re supposed to check if the return value is invalid. Only once you’ve verified that it’s invalid should you check to see if there’s an error.

And as far as I’m been aware, there’s never been any assumption that you’ll get an error. That’s why, throughout your Objective-C code, you always have to check the return value and treat that as gospel.

If you use this method in Swift, the auto-generated Swift signature is:

func data(with string: String?) throws -> Data

Notice something?

I mean, besides the fact that Apple’s compiler/runtime magic smoothly converts between the Objective-C’s last-parameter-is-an-error-pointer pattern and Swift’s “throws” pattern.

The return type doesn’t allow for nil anymore.

You can’t check for an invalid value, if “invalid” means nil.

Instead, you can only assume that the original Objective-C implementation will “throw” an error if there is a problem.

Now, go back to your original Objective-C method. What if you return nil but don’t set the error? What does Swift do?

It does something clever.

In my testing, even when you haven’t set an error, the Swift translation layer throws an error anyway.

If you log it, it’s called nilError.

It’s got a domain of Foundation._GenericObjCError and a code of 0.

Feels a bit like a hack, doesn’t it?

But it does prevent the problem of old Objective-C code not indicating the desired result under Swift.