如何在 Codable 中使用 List 类型? (RealmSwift)

Posted

技术标签:

【中文标题】如何在 Codable 中使用 List 类型? (RealmSwift)【英文标题】:How to use List type with Codable? (RealmSwift) 【发布时间】:2017-08-02 06:17:02 【问题描述】:

问题是List类型不符合Codable,下面的类不能插入Realm。

例如,

class Book: Codable 
    var name: String = ""
    var author: String = ""
    var tags = [String]()

考虑到上面的类符合Codable,如果将这个类存储到Realm,需要使用List<Object>类型而不是[String]

class Book: Object, Codable 
    @objc dynamic var name: String = ""
    @objc dynamic var author: String = ""
    var tags = List<Tag>()

    required convenience init(from decoder: Decoder) throws 
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        author = try container.decode(String.self, forKey: .author)
        tags = try container.decode(List<Tag>.self, forKey: .tags)   // this is problem.
    


class Tag: Object, Codable 
    @objc dynamic var string: String = ""

    required convenience init(from decoder: Decoder) throws 
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        string = try container.decode(String.self, forKey: .string)
    

为了符合 Codable,它应该实现Decodable 协议。 (required convenience init(from decoder: Decoder) throws)

但是,List 类型不符合Codable(Decodable),如果类有List 类型,则无法使用 Codable。

如何解决这个问题?

谢谢,

【问题讨论】:

为 List 写一个扩展,使其符合Codable 【参考方案1】:

我们可以使用扩展使 List 符合 Codable:

extension List : Decodable where Element : Decodable 
    public convenience init(from decoder: Decoder) throws 
        self.init()
        var container = try decoder.unkeyedContainer()
        while !container.isAtEnd 
            let element = try container.decode(Element.self)
            self.append(element)
        
     

extension List : Encodable where Element : Encodable 
    public func encode(to encoder: Encoder) throws 
        var container = encoder.unkeyedContainer()
        for element in self 
            try element.encode(to: container.superEncoder())
        
     

如果其他人需要,我还获得了 RealmOptional 的扩展。

https://gist.github.com/ansonyao/41137bb3cbbca8ef31a13b6bc96ee422

【讨论】:

【参考方案2】:

你快到了。在初始化器中,您可以使用解码后的数组初始化列表。基本上,改变

tags = try container.decode(List<Tag>.self, forKey: .tags)   // this is problem.

let tagsArray = try container.decode([Tag].self, forKey: .tags)   
tags = List(tagsArray) // Now you are good

正如 cmets 中指出的那样,List 构造函数不再像这样工作

你现在想要:

tags.append(objectsIn: tagsArray)

【讨论】:

Realm 3.13.1 中似乎不存在此构造函数【参考方案3】:

接受的答案只有一个问题。 Realm 指定列表应为 let(Constants) 因此,要修改解决方案以遵循最佳实践,您只需将列表设为 let,然后循环将结果附加到您的数组。

// Change this Line in [Your Code]
// to a let (Constant)

var tags = List&lt;Tag&gt;()let tags = List&lt;Tag&gt;()

然后改变

tags = try container.decode(List<Tag>.self, forKey: .tags)

let tagsArray = try container.decode([Tag].self, forKey: .tags)   
tagsArray.forEach tags.append($0) 

【讨论】:

不错的答案,但有一个问题:如果我们将标签附加到此处的列表中,如果对象以某种方式被重用,是否有可能保留旧标签? 我不认为有这样的机会,因为这只在init 上运行。 tagsArray 被实例化为一个新的空 Array 现在如果你有另一个方法可以让你附加到这个 tagsArray 你将拥有所有现有的标签,如果你这样做了,你可能想要一个方法这也可以让你删除标签。但我认为一般来说你应该是安全的 应该使用tags.append(objectsIn: tagsArray)而不是tagsArray.forEach...【参考方案4】:

这就是您创建 Realm / Swift 4 Codable 模型的方式。

有关 JSON 响应、模型和 URLSession 的更多详细信息:Read this article

import RealmSwift

class VendorsList : Object, Decodable 
    @objc dynamic var id : Int = 0
    @objc dynamic var name : String?
    @objc dynamic var logo : String?
    // Create your Realm List.
    var kitchensList = List<VendorKitchens>()

    override class func primaryKey() -> String? 
        return "id"
    

    private enum CodingKeys: String, CodingKey 
        case id
        case name
        case logo
        // Set JSON Object Key
        case kitchensList = "kitchens"

    

    public required convenience init(from decoder: Decoder) throws 
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.name = try container.decode(String.self, forKey: .name)
        self.logo = try container.decode(String.self, forKey: .logo)
        // Map your JSON Array response
        let kitchens = try container.decodeIfPresent([VendorKitchens].self, forKey: .kitchensList) ?? [VendorKitchens()]
        kitchensList.append(objectsIn: kitchens)

    




class VendorKitchens : Object, Decodable 
    @objc dynamic var id : Int = 0
    @objc dynamic var name : String?

    override class func primaryKey() -> String? 
        return "id"
    

    private enum CodingKeys: String, CodingKey 
        case id
        case name
    

【讨论】:

这是可解码不可编码的!如果你可以使用 Codable,你会得到错误:“类型 'VendorsList' 不符合协议 'Encodable'”。并且列表不应该是 var。

以上是关于如何在 Codable 中使用 List 类型? (RealmSwift)的主要内容,如果未能解决你的问题,请参考以下文章

Swift Codable: 服务端新增枚举类型解析失败?

Swift Codable: 服务端新增枚举类型解析失败?

Swift Codable: 服务端新增枚举类型解析失败?

如何一起使用Codable和Coredata?

如何以这种日期格式使用 JSONDecoder / Codable?

无法使用类型为 '(Codable.Type?