原因

AppStorage信任很多人用过,相当于UserDefaults的属性包装器,但比UserDefaults更好用的是能在SwiftUI中作为一个响应式变量同步改写视图。

AppStorage能够存储的类型除了根本类型外,还能够存储一些比较简单的自定义模型,例如:

struct Cat {
    var name: String
    var age: Int
}

想存储自定义模型到AppStorage中,首要该模型得恪守RawRepresentable,而且RawValue要为StringInt类型。

模型转String一般便是转成JSON字符串,因此很自然会想到Codable,运用JSONEncoder编码和JSONDecoder解码:

extension Cat: Codable, RawRepresentable {
    var rawValue: String {
        let jsonData = try! JSONEncoder().encode(self)
        return String(data: jsonData, encoding: .utf8)!
    }
    init?(rawValue: String) {
        let data = rawValue.data(using: .utf8)!
        self = try! JSONDecoder().decode(Self.self, from: data)
    }
}

补充阐明,恪守Codable的模型,如果所有属性都是已经恪守了Codable,那么就不必自己去完成endodedecode

  • PS: Swift提供的根本类型都是Codable的,如StringInt,还有ArrayDictionary(只要存储的元素也是Codable的即可)。

写完,编译没问题,运行,卡死!!!

What the hell?

直接揭晓答案,这是因为死循环了,看看swift的源码

【Swift】同时使用 RawRepresentable 和 Codable 的注意点

能够看出,只要恪守了CodableRawRepresentable,其默许完成的endode里边会调用rawValue,而rawValue是刚刚自己重写的计算属性,内部则调用了JSONEncoder().encode(self),因此造成死循环了。

解决方法

自己去完成endodedecode

extension Cat: Codable {
    enum CodingKeys: String, CodingKey {
        case name, age
    }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        name = try c.decode(String.self, forKey: .name)
        age = try c.decode(Int.self, forKey: .age)
    }
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        try c.encode(name, forKey: .name)
        try c.encode(age, forKey: .age)
    }
}

打破循环!

最终

这个问题最主要的原因是过于依靠Swift的便捷性(默许完成Codable),真没想到CodableRawRepresentable一同运用时,会被这些默许完成导致死循环,还好这种问题开发时就能发现,今后运用新东西还是要多做测验,避免这种问题上线后才发现。

总结一下:运用AppStorage存储自定义模型,模型需要恪守RawRepresentable而且RawValue要为String,如果运用Codable进行模型转换,要自己去完成endodedecode避免死循环。