原因
AppStorage
信任很多人用过,相当于UserDefaults
的属性包装器,但比UserDefaults
更好用的是能在SwiftUI
中作为一个响应式变量同步改写视图。
AppStorage
能够存储的类型除了根本类型外,还能够存储一些比较简单的自定义模型,例如:
struct Cat {
var name: String
var age: Int
}
想存储自定义模型到AppStorage
中,首要该模型得恪守RawRepresentable
,而且RawValue
要为String
或Int
类型。
模型转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
,那么就不必自己去完成endode
和decode
。
- PS:
Swift
提供的根本类型都是Codable
的,如String
、Int
,还有Array
和Dictionary
(只要存储的元素也是Codable
的即可)。
写完,编译没问题,运行,卡死!!!
What the hell?
直接揭晓答案,这是因为死循环了,看看swift的源码:
能够看出,只要恪守了Codable
和RawRepresentable
,其默许完成的endode
里边会调用rawValue
,而rawValue
是刚刚自己重写的计算属性,内部则调用了JSONEncoder().encode(self)
,因此造成死循环了。
解决方法
自己去完成endode
和decode
:
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
),真没想到Codable
和RawRepresentable
一同运用时,会被这些默许完成导致死循环,还好这种问题开发时就能发现,今后运用新东西还是要多做测验,避免这种问题上线后才发现。
总结一下:运用AppStorage
存储自定义模型,模型需要恪守RawRepresentable
而且RawValue
要为String
,如果运用Codable
进行模型转换,要自己去完成endode
和decode
避免死循环。