How do Swift errors and Objective-C NSError translate across the border?
Predict first
What does this print? (failAndReturnError: is implemented in Objective-C and sets code 7 in the domain "demo".)
do {
try Thing().fail()
} catch {
let e = error as NSError
print(e.code, e.domain)
}
- 0 demo
- 7 demo
- Compile error
Predict first
What does this print?
enum MyError: Error { case first, second }
print((MyError.second as NSError).code)
- 0
- 1
- 2
Mental model. A Swift error is a typed incident report. The Objective-C side only understands a numbered ticket, so the translator assigns a ticket number from the case order unless you tell it otherwise.
Objective-C reports failure with an NSError ** out-parameter and a return value (BOOL or a nullable object). Swift imports such a method as throws: the error parameter disappears, the BOOL becomes Void, and a trailing AndReturnError in the selector is dropped, so failAndReturnError: becomes fail(). Going the other way, a Swift throws method marked @objc is exposed with the NSError ** parameter, and the thrown error is converted.
The conversion is automatic: any Swift Error becomes an NSError when it reaches Objective-C, with a domain taken from the type name and a code equal to the case’s index. That makes codes fragile, so adopt CustomNSError (and LocalizedError for messages) to fix the domain and code, or use NS_ERROR_ENUM on the Objective-C side so named errors import cleanly. Since a Swift throw crosses into Objective-C only as an NSError, avoid relying on the exact Swift error type surviving.
Swift imports the NSError ** method as a throwing call, and the caught error carries the domain and code the Objective-C code set.
Without CustomNSError the code is the case’s index, so the second case is 1. Reordering cases would change it.
Common traps
- Relying on the auto-generated code. Reordering enum cases changes the numbers that ObjC callers see.
- Passing
nilforerror:and ignoring the return value. The method signalled failure through the return, and it is now lost.
They might follow up with
- How do you give an error a custom domain? Conform to
CustomNSErrorand returnerrorDomainanderrorCode. - What does the
AndReturnErrorsuffix convention do? It marks the method as error-returning so Swift can import it asthrowswith a clean name.