First, note that this limitation exists only for class
es, so the example initializer will work for as-is for struct
s and enum
s, but not all situations allow changing a class
to one of these types.
This limitation on class
initializers is a frequent pain-point that shows up often on this site (for example). There is a thread on the Swift forums discussing this issue, and work has started to add the necessary language features to make the example code above compile, but this is not complete as of Swift 5.4.
From the thread:
Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.
Using this idea to fix the example code yields
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
}
protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
which works fine. If Test
is the only type conforming to TestProtocol
, then only Test
will get this initializer.
An alternative is to simply extend Decodable
or another protocol to which your class conforms, but this may be undesirable if you do not want other types conforming to that protocol to also get your initializer.
init
". – Quintillainit
, but more relevantly, you may not like factory methods, or you may need it to be an initializer for protocol conformance reasons. – Certificate