This is not true — classes can definitely inherit initializers. See the "Automatic Initializer Inheritance" section from the Swift language guide for more info on that. To clarify this case specifically here:
- For a base class conforming to
Decodable (e.g. your BaseClass), you have two options: manually implement Decodable, or allow the compiler to synthesize it for you, if possible
- For a class adopting
Decodable inheriting from a class which does not (e.g. Bar in class Foo {} class Bar : Foo, Decodable {}) you have the same two options: manually implement Decodable, or allow the compiler to synthesize
- For a subclass of a class already adopting
Decodable (e.g. your SuperClass), you have to options: override your superclass's implementation by offering your own, or inheriting it, if possible. There is no way for the compiler to synthesize an implementation here for you
To show off this last case, let's implement BaseClass and SuperClass manually with the same code that we'd expect compiler to otherwise generate:
import Foundation
class BaseClass : Decodable {
var foo: String
init(foo: String) { self.foo = foo }
private enum CodingKeys: String, CodingKey {
case foo
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
foo = try container.decode(String.self, forKey: .foo)
}
}
class SuperClass : BaseClass {
var bar: Int
init(foo: String, bar: Int) {
self.bar = bar
super.init(foo: foo)
}
private enum CodingKeys: String, CodingKey {
case bar
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bar = try container.decode(Int.self, forKey: .bar)
try super.init(from: container.superDecoder())
}
}
let decoder = JSONDecoder()
let json = """
{
"bar": 42,
"super": { "foo": "Hello, world!" }
}
""".data(using: .utf8)!
let superClass = try! decoder.decode(SuperClass.self, from: json)
print(superClass.foo, superClass.bar) // Hello, world! 42
This prints out "Hello, world! 42", as we might expect. Now, let's try removing BaseClass.init(from:) to allow the compiler to synthesize the implementation:
class BaseClass : Decodable {
var foo: String
init(foo: String) { self.foo = foo }
}
// ...
print(superClass.foo, superClass.bar) // => Hello, world! 42
So far, no change in behavior — great! Now, let's try the same with SuperClass:
class BaseClass : Decodable {
var foo: String
init(foo: String) { self.foo = foo }
}
class SuperClass : BaseClass {
var bar: Int
init(foo: String, bar: Int) {
self.bar = bar
super.init(foo: foo)
}
}
// ...
This time, we get
error: 'required' initializer 'init(from:)' must be provided by subclass of 'BaseClass'
}
^
<unknown>:0: note: 'required' initializer is declared in superclass here
This is the error message you get when you need to provide an implementation for a required initializer but have provided a different initializer, and new requirements on the class prevent inheritance. For instance:
class BaseClass {
required init() {} // note: 'required' initializer is declared in superclass here
}
class SuperClass : BaseClass {
var foo: Int
init(foo: Int) { self.foo = foo }
} // error: 'required' initializer 'init()' must be provided by subclass of 'BaseClass'
If SuperClass didn't provide its own init(foo:) you'd get a different error message saying it has no initializers (your original error message above). In any case, we can get around this by providing foo with a default value instead of assigning in an init:
class BaseClass {
required init() {}
}
class SuperClass : BaseClass {
var foo: Int = 3
}
This allows inheritance of required init() while still initializing SuperClass.foo. Let's apply that to our situation as well:
class BaseClass : Decodable {
var foo: String = "This is some default"
}
class SuperClass : BaseClass {
var bar: Int = 3
}
// ...
This compiles just fine — but, does SuperClass here get its own init(from:), or is it inheriting BaseClass.init(from:)? If we try with the same JSON payload, we get the following error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "foo", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"foo\", intValue: nil) (\"foo\").", underlyingError: nil))
This error boils down to: "we tried to find "foo" as a key in the root dictionary (codingPath is empty), but failed to find it". Which is true, because the JSON payload we're using has foo nested:
{
"bar": 42,
"super": { "foo": "Hello, world!" }
}
For the sake of the test, let's un-nest to see what happens:
let json = """
{
"bar": 42,
"foo": "Hello, world!"
}
""".data(using: .utf8)!
let superClass = try! decoder.decode(SuperClass.self, from: json)
print(superClass.foo, superClass.bar) // Hello, world! 3
We're now getting bar's default value rather than the value in the JSON — that's because SuperClass has inherited BaseClass.init(from:) directly, and BaseClass.init(from:) has no knowledge of bar; SuperClass has to then use bar's default value.
If, by the way, we tried to remove the default value from bar, we'd see the following errors:
error: class 'SuperClass' has no initializers
class SuperClass : BaseClass {
^
note: did you mean to override 'init(from:)'?
class SuperClass : BaseClass {
^
note: stored property 'bar' without initial value prevents synthesized initializers
var bar: Int
^
= 0
That first note is the relevant one: we can't inherit init(from:) so we would otherwise need to override it. (The "synthesized initializers" note refers only to synthesizing a default constructor of init(), not init(from:).
This case would be improved if the compiler could synthesize SuperClass.init(from:) instead of inheriting, but it won't be able to without a refactor of Swift's protocol conformance and inheritance system (and without syntax to disambiguate between "I'm not providing an implementation because I'd like to inherit" vs. "I'm not providing an implementation because I'd like to synthesize")