Skip to content

Instantly share code, notes, and snippets.

@dabrahams
Last active October 22, 2022 13:46
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dabrahams/b3413352d9765eeaa932d19e6fd2787b to your computer and use it in GitHub Desktop.
Save dabrahams/b3413352d9765eeaa932d19e6fd2787b to your computer and use it in GitHub Desktop.
Class factory initializers
/// Classes whose initializers actually create derived classes
protocol FactoryInitializable {
/// The type of the least-derived class declared to be FactoryInitializable.
///
/// - Warning: Do not define this in your FactoryInitializable type!
associatedtype FactoryBase: AnyObject, FactoryInitializable = Self
// This associatedtype is a trick that captures `Self` at the point where
// `FactoryInitializable` enters a class hierarchy; in other contexts, `Self`
// refers to the most-derived type.
}
extension FactoryInitializable where Self: AnyObject {
/// Optimally “creates” an instance that is just another reference to `me`.
///
/// - Requires: `me is Self`.
///
/// Taking `FactoryBase` as a parameter prevents, at compile-time, the
/// category of bugs where `me` is not derived from the least-derived ancestor
/// of `Self` conforming to `FactoryInitializable`.
///
/// However, there are still ways `me` might not be derived from `Self`. If
/// you have factory initializers at more than one level of your class
/// hierarchy and you can't control exactly what is passed here, use
/// `init(aliasing:)` instead.
init(unsafelyAliasing me: FactoryBase) {
self = unsafeDowncast(me, to: Self.self)
}
/// Safely “creates” an instance that is just another reference to `me`.
///
/// - Requires: `me is Self`.
init(aliasing me: FactoryBase) {
self = me as! Self
}
}
public class Base : FactoryInitializable {
/// Constructs an instance whose dynamic type depends on the value of `one`
public convenience init(_ one: Bool) {
self.init(unsafelyAliasing: one ? Derived1() : Derived2())
}
}
internal class Derived1 : Base {
override public init() { super.init() }
}
internal class Derived2 : Base {
override public init() { super.init() }
}
// Inspect the codegen for testMe1 and testMe2 to see that we are getting
// optimal results.
@inline(never)
func testMe1(_ one: Bool) -> Base {
Base(one)
}
@inline(never)
func testMe2(_ one: Bool) -> Base {
one ? Derived1() : Derived2()
}
print(testMe1(true))
print(testMe2(true))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment