How do Objective-C initializers and factory methods import, and what do designated initializers and NS_UNAVAILABLE change?
Predict first
Using the header described in the comments, what does this print?
// Objective-C: -initWithOwner: (designated) +accountForOwner: +guestAccount
// -initWithOwner:balance: -init is NS_UNAVAILABLE
let a = Account(owner: "ana")
let b = Account(forOwner: "bo")
let g = Account.guest()
let c = Account(owner: "cy", balance: 5)
print(a.owner, b.owner, g.owner, c.balance)
- ana bo guest 5
- It does not compile: guest() does not exist
- ana bo guest 0
Predict first
What happens with let x = Account() when -init is NS_UNAVAILABLE?
// Objective-C: - (instancetype)init NS_UNAVAILABLE;
let x = Account()
- Compiles; x has an empty owner
- Compile error: init() is unavailable
Mental model. The importer reads the selector aloud as a sentence. “init with owner” becomes “the initializer whose label is owner”; “account for owner” becomes “make an account, for owner”.
An -initWithOwner: method imports as init(owner:): the initWith prefix is dropped and the first label comes from the rest of the selector. instancetype imports as the class (Self for subclasses). A class factory method is also turned into an initializer when its name starts with the type name: +accountForOwner: becomes Account(forOwner:). A factory that does not follow the pattern, such as +guestAccount, stays a static method and just loses the repeated type name: Account.guest(). Naming the old spelling is a compile error that suggests the new one.
Swift also enforces Objective-C’s designated initializer rules. NS_DESIGNATED_INITIALIZER marks the one every other initializer must call, and a Swift subclass has to follow the usual two-phase rules around it. NS_UNAVAILABLE on -init (or +new) removes it from Swift as well, so Account() fails to compile instead of producing an object with no owner. Use that when an object cannot exist without its dependencies, and remember +new is just alloc plus init and imports as init().
The three spellings come from three different import rules: an initWith method, a factory that starts with the type name, and a factory that does not.
The attribute carries across the border, so the mistake is caught at compile time.
Common traps
- Forgetting
NS_UNAVAILABLEoninit. Swift then lets callers build half-configured objects, and nullability annotations on the other initializers lie. - Assuming every
+thingWith…becomesinit. Only names that begin with the type name do.
They might follow up with
- What does
NS_REFINED_FOR_SWIFTdo to an initializer? It imports with a leading double underscore so you can wrap it in a nicer one. - Why must a Swift subclass of an Objective-C class call
super.init(…)with the designated initializer? The superclass’s invariants are set up there.
Related questions
- 4.5.3 How do you make an Objective-C API feel native in Swift?
- 4.8.1 How do Swift and Objective-C see each other: bridging header, umbrella header, and the -Swift.h file?
← How do NS_ENUM, NS_OPTIONS and NS_CLOSED_ENUM import, and why does… What does atomic actually guarantee for an Objective-C property, and… →