在开发中,会经常遇到拷贝数据对象的情况,也会遇到保存数据到本地的情况,
如果是用OC的话,无论是copyWithZone
还是归档解档,用runtime
都可以很好地解决这个问题
在Swift中,不直接用runtime该怎么实现呢?
答案就是Codable
typealias Codable = Decodable & Encodable
Codable其实就是Decodable和Encodable
让需要实现解归档的类遵循Codable
协议
然后用JSONEncoder
来实现解归档
class TestClassAAA: Codable {
var a = 0
func copy() -> Self {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else {
fatalError("encode failed")
}
let decoder = JSONDecoder()
guard let target = try? decoder.decode(Self.self, from: data) else {
fatalError("decode failed")
}
return target
}
}
如果我们用很多对象都需要实现Codable呢?
可以用通用协议及默认实现
protocol ATCodable: Codable {
func copy() -> Self
}
extension ATCodable {
func copy() -> Self {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else {
fatalError("encode failed")
}
let decoder = JSONDecoder()
guard let target = try? decoder.decode(Self.self, from: data) else {
fatalError("decode failed")
}
return target
}
}
这样只需要遵循该协议即可实现copy
、解归档了
这只是开始,实际项目中会有各种各样的异常情况
一、枚举
如果类或者结构体里有枚举属性,再去遵循Codable
协议,则会报错,因为枚举是默认未遵循Codable
的
这里我们需要去兼容下枚举类型
同样去写个通用协议去遵循Codable
protocol CodableEnumeration: RawRepresentable, Codable where RawValue: Codable {
static var defaultCase: Self { get }
}
extension CodableEnumeration {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let decoded = try container.decode(RawValue.self)
self = Self.init(rawValue: decoded) ?? Self.defaultCase
} catch {
self = Self.defaultCase
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
然后所有需要遵循Codable
的枚举去遵循CodableEnumeration
并填上默认值defaultCase
即可
系统枚举或者第三方库的枚举也同样适用
二、结构体
结构体分为自己写的和别人写的
自己写的结构体就很简单了,直接遵循ATCodable
即可
如果是别人写的,比如第三方库的struct
或者系统的struct
直接遵循ATCodable
会报错 "Implementation of 'Decodable' cannot be automatically synthesized in an extension in a different file to the type"
也就是不能在extension
或其他文件里去自动实现Codable
这种情况,我们只能去重写Codable
的实现了
extension XXXRect: ATCodable {
private enum CodingKeys:String,CodingKey{
case left
case right
case bottom
case top
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy:CodingKeys.self)
try container.encode(left,forKey:.left)
try container.encode(right,forKey:.right)
try container.encode(bottom,forKey:.bottom)
try container.encode(top,forKey:.top)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let left = try container.decode(Float.self,forKey:.left)
let right = try container.decode(Float.self,forKey:.right)
let bottom = try container.decode(Float.self,forKey:.bottom)
let top = try container.decode(Float.self,forKey:.top)
self = NvsRect(left: left, right: right, bottom: bottom, top: top)
}
}
如果类中或者结构体中并不是所有属性都需要解归档,那就只把需要解归档的属性写在CodingKeys
里,然后手动去实现即可
三、class
同样,如果是自己写的class
,直接遵循ATCodable
即可
但如果是别人写的,比如第三方库的class
或者系统的class
,也无法直接遵循ATCodable
,而且也无法去实现
比如我们的class
里有个UIImage
属性,该怎么处理呢?
要么就是重写Codable
private enum CodingKeys : String, CodingKey {
case thumImage
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let data = try? container.decode(Data.self, forKey: .thumImage) {
thumImage = UIImage(data: data)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let image = thumImage {
try container.encode(image.pngData(), forKey: .thumImage)
}
}
如果自己实现了CodingKeys
,则JsonDecoder
是只会帮你解归档CodingKeys
里的key的属性,其余的会忽略
如果不实现,则会默认解归档所有属性
所以这样同样有弊端,为了兼容一个UIImage
,其他属性都得手动去实现Codable
那就换一个思路,可以将该image改造成计算属性,重新加一个默认实现Codable的Data来代替其去解归档
var thumImage: UIImage? {
get {
UIImage(data: thumImageData ?? Data())
}
set {
thumImageData = newValue?.pngData()
}
}
var thumImageData: Data?
其他的class
也可以用同样的方式,比如PHAsset
用其localIdentifier
代替,UIColor
用Int
代替等等
四、其它
一些基础数据类型Int,Float,Bool
自然是直接就能解归档,字符串也一样
数组也是只要其element
遵循Codable
协议即可