mitchtreece
mitchtreece

Reputation: 181

Swift 4 Codable Realm Object Subclasses

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:

I'm not sure what the correct way is to go about fixing these issues.

Upvotes: 2

Views: 3001

Answers (3)

Andy Dent
Andy Dent

Reputation: 17969

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.

Upvotes: 0

kishikawa katsumi
kishikawa katsumi

Reputation: 10573

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)
    }
}

Upvotes: 4

Santhosh R
Santhosh R

Reputation: 1568

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.

Upvotes: 0

Related Questions