问题
trying to switch some of my codebase over to Swift 4's new nifty Codable
protocol. My setup looks something like this:
class Base: Object, Codable {
dynamic var id: String = ""
dynamic var timestamp: String = ""
private enum CodingKeys: String, CodingKey {
case id = "_id"
case timestamp = "timestamp"
}
}
class User: Base {
dynamic var name: String = ""
private enum CodingKeys: String, CodingKey {
case name = "name"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
try super.init(from: decoder)
}
}
I have a base realm object class that conforms to Codable
, and a subclass of Base
that also has it's own coding keys. I override init(decoder:)
on the User
subclass to map my additional coding keys, then call super.init(decoder:)
to map Base
's coding keys. However, once I override init(decoder:)
I get the following errors:
- required initializer 'init()' must be provided by subclass of 'Base'
- required initializer 'init(realm:schema:)' must be provided by subclass of 'Base'
- required initializer 'init(value:schema:)' must be provided by subclass of 'Base'
I'm not sure what the correct way is to go about fixing these issues.
回答1:
You cannot override init()
or other initializers of Realm Object
. You can use convenience initializer instead. Then you cannot call super.init(from:)
, so define a method that decodes superclass' properties like Base.decode(from:)
.
See following code sample:
class Base: Object, Codable {
dynamic var id: String = ""
dynamic var timestamp: String = ""
private enum CodingKeys: String, CodingKey {
case id = "_id"
case timestamp = "timestamp"
}
func decode(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.timestamp = try container.decode(String.self, forKey: .timestamp)
}
}
class User: Base {
dynamic var name: String = ""
private enum CodingKeys: String, CodingKey {
case id = "_id"
case timestamp
case name = "name"
}
required convenience init(from decoder: Decoder) throws {
self.init()
try decode(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
回答2:
You cannot override just one initializer of a class. If you are going to override, do it for all of them. If you don't really use or care about the other initializers just at least call super.<respective init>
in them.
For init(realm: RLMRealm, schema: RLMObjectSchema)
and init(value: Any, schema: RLMSchema)
compiler is going to complain that it doesn't know what RLMRealm, RLMObjectSchema and RLMSchema are. So import Realm besides RealmSwift.
回答3:
As I just answered on another question, whilst you may be able to use your subclass as a Codable type with Katsumi's advice above, you may run into another gotcha.
You cannot have collections of Base
as a reference type that contain subclass instances and have that collection survive Codable. It will only decode as Base
instances.
Polymorphic persistence appears to be broken by design.
The bug report SR-5331 quotes the response they got on their Radar.
Unlike the existing NSCoding API (NSKeyedArchiver), the new Swift 4 Codable implementations do not write out type information about encoded types into generated archives, for both flexibility and security. As such, at decode time, the API can only use the concrete type your provide in order to decode the values (in your case, the superclass type).
This is by design — if you need the dynamism required to do this, we recommend that you adopt NSSecureCoding and use NSKeyedArchiver/NSKeyedUnarchiver
I am unimpressed, having thought from all the glowing articles that Codable was the answer to some of my prayers. A parallel set of Codable structs that act as object factories is one workaround I'm considering, to preserve type information.
来源:https://stackoverflow.com/questions/47080785/swift-4-codable-realm-object-subclasses