How do Swift errors and Objective-C NSError translate across the border?

4.4.3 · L3 · Interview

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 nil for error: 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 CustomNSError and return errorDomain and errorCode.
  • What does the AndReturnError suffix convention do? It marks the method as error-returning so Swift can import it as throws with a clean name.

Related questions

  • 4.4.2 Which Swift features cannot be seen from Objective-C?
  • 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?

← Which Swift features cannot be seen from Objective-C?