// NOTE: this first attempt has been superseded by a slightly better one: // https://gist.github.com/masonmark/967ec16161d6e0b26e146f32c2f35898 // Mason 2019-09-28: This is one way to allow subclasses to inherit a // method with a parameter whose type refers to the actual subclass type // (and not the parent class's type). // // One purpose here is to define a base class, that can have various // subclasses, and enable all of the subclasses to inherit a class method // named configure(), which creates an instance of the subclass and then // configures the instance and its various subclass-specific properties. // // This turned out not to be so straighforward, so there is some convoluted // set up code to make it work. // // I am not sure this is the best way to do this in Swift 5.1; it is just // the first way I found that worked. protocol BaseConfigurable { // static func configure( configurator: (Self) -> Void) -> Self; // // We cannot have the above definition in the protocol itself. It // must be defined only in the protocol extension, otherwise we get // error: // // Protocol 'BaseConfigurable' requirement 'configure(configurator:)' cannot // be satisfied by a non-final class ('Configurable') because it uses 'Self' // in a non-parameter, non-result type position } extension BaseConfigurable where Self: Configurable { static func configure( _ configurator: (Self) -> Void) -> Self { let result = Self.init(); configurator(result); return result; } } class Configurable: BaseConfigurable { required init() { // We need this required init, otherwise we get this error: // // Constructing an object of class type 'Self' with a metatype // value must use a 'required' initializer } } class Person: Configurable { var name = "Alice" var age = 0 var bio: String { "My name is \(name), and I am a \(Self.self). I am \(age) years old." } } class PoliceOfficer: Person { var badgeNumber: String = "0000000" } class Lieutenant: PoliceOfficer { var hasBeard = true var cigarCount = 0 } class FireFighter: Person { var helmetSize: Int? var hasLicenseToDriveFireEngine = false } let lisa = Person.configure() { lisa in lisa.name = "Lisa" lisa.age = 39 } let fred = Lieutenant.configure() { fred in fred.name = "Fred" fred.age = 57 fred.hasBeard = false } // let biff = FireFighter.configure() { biff in // biff.hasLicenseToDriveFireEngine = true; // biff.helmetSize = 15 // biff.age = 21 // biff.name = "Biff Wigginberger Jr." // } // instead of the above, do it another way for example: func configureBiff(biff: FireFighter) { biff.hasLicenseToDriveFireEngine = true; biff.helmetSize = 15 biff.age = 21 biff.name = "Biff Wigginberger Jr." } let biff = FireFighter.configure(configureBiff) print(lisa.bio) print(fred.bio) print(biff.bio) // prints: // My name is Lisa, and I am a Person. I am 39 years old. // My name is Fred, and I am a Lieutenant. I am 57 years old. // My name is Biff Wigginberger Jr., and I am a FireFighter. I am 21 years old.