In Swift's implementation today, all class initializers are split into "allocating" and "initializing" entry points. The allocating entry point is what gets called when you write an initializer call like ClassName()
in Swift; it allocates the object, and passes the uninitialized memory buffer to the initializing entry point to perform the initialization. The initializing entry point corresponds to what you write in an init() { ... }
body, assigning initial values to all stored properties and performing other necessary setup.
This split resembles the +alloc
/-init
convention in Objective-C, and C++ implementations split initializers in similar ways. It is fundamentally necessary for designated initializers, since a designated initializer for a subclass must be able to allocate an instance of the subclass and then chain to a designated initializer of the base class to perform the initialization of only the base part. It is however a constraining and inefficient design for convenience initializers, which always delegate to another peer initializer to perform the entire object initialization. The initializer that a convenience initializer delegates to could be a protocol extension initializer, a C or Objective-C factory function imported as an initializer, or (in the fullness of time) a Swift factory initializer, and in these situations, the convenience initializer must throw away the allocated object it was given so that the delegated initializer can allocate a new one. It would be a better pure Swift ABI for convenience initializers to have only the "allocating" entry point, and leave allocation as the responsibility of the initializer they ultimately delegate to.
However, Swift initializers can still be exported as @objc
for Cocoa interop, and in that case, we must still present a +alloc
/-init
split for consistency with Objective-C conventions. If we change the ABI for Swift convenience initializers to always perform allocation, then there could be a performance hit for the Objective-C -init
interfaces to these initializers, since they will always have to deallocate and reallocate a new object, whereas that is currently avoided as long as the Swift convenience initializer eventually delegates to a designated initializer. We have a few options here:
- If an initializer is
@objc
, keep the existing ABI. This would be unfortunate, since up until now,@objc
in classes has not affected Swift ABI and can be freely added without breaking Swift binary compatibility. - If an initializer is
@objc
, have the public ABI be the allocating entry point, but keep an internal initializing entry point. This would allow@objc
thunks within a resilience domain to avoid the reallocation. If an initializer delegates to another convenience initializer in a different module, it would still have to reallocate. - Accept the performance hit for
@objc
interfaces.
Any alternatives I missed? My gut feeling is that class instance allocation is not generally something you want in a hot loop to begin with, but I wanted to get feedback before committing to an approach. Thanks!