【问题标题】:Swift 4 Codable Realm Object SubclassesSwift 4 可编码领域对象子类
【发布时间】:2017-11-02 16:59:49
【问题描述】:

试图将我的一些代码库切换到 Swift 4 的新漂亮 Codable 协议。我的设置如下所示:

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)

    }

}

我有一个符合Codable 的基本领域对象类,以及一个Base 的子类,它也有自己的编码键。我在User 子类上覆盖init(decoder:) 以映射我的其他编码键,然后调用super.init(decoder:) 来映射Base 的编码键。但是,一旦我覆盖了init(decoder:),我就会收到以下错误:

  • 必需的初始化程序“init()”必须由“Base”的子类提供
  • 必需的初始化程序“init(realm:schema:)”必须由“Base”的子类提供
  • 必需的初始化程序“init(value:schema:)”必须由“Base”的子类提供

我不确定解决这些问题的正确方法是什么。

【问题讨论】:

标签: ios swift realm codable


【解决方案1】:

您不能覆盖 init() 或 Realm Object 的其他初始化程序。您可以改用便捷初始化程序。那么你不能调用super.init(from:),所以定义一个解码超类属性的方法,比如Base.decode(from:)

参见以下代码示例:

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】:

    你不能只覆盖一个类的初始化器。如果您要覆盖,请为所有这些覆盖。如果您并不真正使用或关心其他初始化程序,至少只需在其中调用 super.<respective init>
    对于init(realm: RLMRealm, schema: RLMObjectSchema)init(value: Any, schema: RLMSchema),编译器会抱怨它不知道RLMRealm、RLMObjectSchema 和RLMSchema 是什么。所以除了 RealmSwift 之外还要导入 Realm。

    【讨论】:

      【解决方案3】:

      正如我刚刚在 another question, 上回答的那样,虽然您可以按照上面 Katsumi 的建议将您的子类用作 Codable 类型,但您可能会遇到另一个问题。

      您不能将 Base 的集合作为包含子类实例的引用类型并使该集合在 Codable 中存在。它只会解码为Base 实例。

      多态持久性似乎被设计破坏了

      错误报告SR-5331 引用了他们在Radar. 上得到的回复

      与现有的 NSCoding API (NSKeyedArchiver) 不同,为了灵活性和安全性,新的 Swift 4 Codable 实现不会将有关编码类型的类型信息写入生成的档案中。因此,在解码时,API 只能使用您提供的具体类型来解码值(在您的情况下是超类类型)。

      这是设计使然——如果您需要执行此操作所需的动力,我们建议您采用 NSSecureCoding 并使用 NSKeyedArchiver/NSKeyedUnarchiver

      我不为所动,从所有精彩的文章中想到 Codable 是我的一些祈祷的答案。作为对象工厂的一组并行 Codable 结构是我正在考虑的一种解决方法,以保留类型信息。

      【讨论】: