Swift's automatically provided memberwise initializer is a powerful feature. However, when an explicit initializer is present, Swift omits its memberwise initializer:
struct Person: Decodable {
let name: String
enum CodingKeys: CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
_ = Person.init(name: "Foo") // 🛑 Compiler error; Swift won't add a memberwise init
A common workaround is the “extension dance,” which will retain Swift’s memberwise initializer:
struct Person {
let name: String
}
extension Person: Decodable {
enum CodingKeys: CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
_ = Person.init(name: "Foo") // 🎉 Still has memberwise init
Using extensions preserves the memberwise initializer, but dictates tradeoffs:
- Fragmentation: Using extensions can split up related code, potentially reducing readability by distributing a single concept over multiple locations.
- Boilerplate: Protocol conformances via extensions add more repetitive code, which can obscure the primary logic and functionality.
- Obscured Conformance: Distributing conformances across multiple extensions can make it harder to immediately identify all the protocols a type conforms to.
- Pattern Rigidity: The extension approach dictates a particular design pattern, which might not always be the most suitable for every use-case.
For the convenience of generated memberwise initializers without the tradeoffs of the extension approach (and more), see MemberwiseInit.