Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save masonmark/9036c1767ffef24c15e450478861cdde to your computer and use it in GitHub Desktop.
Save masonmark/9036c1767ffef24c15e450478861cdde to your computer and use it in GitHub Desktop.
One way to let a subclass inherit a method referencing its own type in Swift
// 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment