1)發現問題
在我剛進入公司接手公司專案(Swift)時,發現模型檔案中存在大量 encode 和 decode 程式碼,是用於模型的歸檔和解檔(這些基礎知識我就不補充了),每當模型增加或刪除一個屬性時,都需要增加或刪除對應的 encode 和 decode 程式碼,類似於下面:
class UserModel: NSObject, NSCoding {
var name: String?
var age: Int = 0
var isWriter: Bool = false
var createAt: Double = 0
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "user_name") as? String
age = aDecoder.decodeInteger(forKey: "user_age")
isWriter = aDecoder.decodeBool(forKey: "user_is_writer")
createAt = aDecoder.decodeDouble(forKey: "user_created_at")
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "user_name")
aCoder.encode(age, forKey: "user_age")
aCoder.encode(isWriter, forKey: "user_is_writer")
aCoder.encode(isWriter, forKey: "user_created_at")
}
}
複製程式碼
當這樣的模型越來越多,我們就要維護越來越多這樣的東西,這樣是沒有什麼意義的,所以我在想,怎麼樣才能不用給每個模型都加這些程式碼就能實現這個功能呢?或者怎麼才能簡化這些程式碼?
2)分析問題
首先我先觀察這些程式碼,發現這些程式碼核心就只有 2 個,就是 decode 和 encode,decode 需要設定屬性的值,encode 需要獲取屬性的值,有點類似我們 objc 屬性的 get、set 方法,需要我們拿到該模型的所有儲存屬性的型別,判斷是哪種型別,呼叫哪種 decode 和 encode 方法。而要獲取該模型的所有儲存屬性的型別,我想到了 Mirror 反射(對應 Obj-C 的執行時 runtime )。
3)解決問題
首先我先構建一個模型基類 BasicCodingModel,用於 decode 和 encode 功能的整合
class BasicCodingModel: NSObject, NSCoding {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
// 這個要先呼叫 super.init ,因為 decodeMirror 裡用到了 self,否則會報錯
super.init()
decodeMirror(coder: aDecoder)
}
func encode(with aCoder: NSCoder) {
encodeMirror(coder: aCoder)
}
// 用來確保獲取和設定的 key 是一致的
fileprivate func getCodingKey(_ label: String) -> String {
// 這裡直接用屬性名當 key
return label
}
// 解碼的反射應用
fileprivate func decodeMirror(coder aDecoder: NSCoder) {
// 建立當前模型的反射
let mirror: Mirror = Mirror(reflecting: self)
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)
mirror.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
var decodeValue: Any?
if value is Bool {
decodeValue = aDecoder.decodeBool(forKey: decodeKey)
} else if value is Int {
decodeValue = aDecoder.decodeInteger(forKey: decodeKey)
} else if value is Double {
decodeValue = aDecoder.decodeDouble(forKey: decodeKey)
} else {
decodeValue = aDecoder.decodeObject(forKey: decodeKey)
}
if let decodeValue = decodeValue {
// 通過使用 KVC 的方式對屬性進行賦值
self.setValue(decodeValue, forKeyPath: label)
}
}
}
}
// 編碼的反射應用
fileprivate func encodeMirror(coder aCoder: NSCoder) {
// 建立當前模型的反射
let mirror: Mirror = Mirror(reflecting: self)
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)
mirror.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
if let valueBool = value as? Bool {
// 具體呼叫的是 encode(_ value: Bool, forKey key: String)
aCoder.encode(valueBool, forKey: decodeKey)
} else if let valueInt = value as? Int {
// 具體呼叫的是 encode(_ value: Int, forKey key: String)
aCoder.encode(valueInt, forKey: decodeKey)
} else if let valueDouble = value as? Double {
// 具體呼叫的是 encode(_ value: Double, forKey key: String)
aCoder.encode(valueDouble, forKey: decodeKey)
} else {
// 具體呼叫的是 encode(_ object: Any?, forKey key: String)
aCoder.encode(value, forKey: decodeKey)
}
}
}
}
}
複製程式碼
然後我們之前的 UserModel 就可以寫成以下的格式
class UserModel: BasicCodingModel {
var name: String?
var age: Int = 0
var isWriter: Bool = false
var createAt: Double = 0
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
複製程式碼
我們來測試一下,在專案中啟動會執行的地方放入下面程式碼:
fileprivate func test() {
// 建立一個模型
let model = UserModel()
model.name = "執著執念"
model.age = 24
model.createAt = Date().timeIntervalSince1970
model.isWriter = true
// 歸檔
print("====== archivedData ======")
let data = NSKeyedArchiver.archivedData(withRootObject: model)
print("name=\(model.name)")
print("tag=\(model.age)")
print("is_writer=\(model.isWriter)")
print("craete_at=\(model.createAt)")
// 解檔
print("\n====== unarchiveObject ======")
if let unarchiveModel = NSKeyedUnarchiver.unarchiveObject(with: data) as? UserModel {
print("name=\(unarchiveModel.name)")
print("tag=\(unarchiveModel.age)")
print("is_writer=\(unarchiveModel.isWriter)")
print("craete_at=\(unarchiveModel.createAt)")
} else {
print("unarchiveModel error")
}
}
/*
列印如下:
====== archivedData ======
name=Optional("執著執念")
tag=24
is_writer=true
craete_at=1521365431.49331
====== unarchiveObject ======
name=Optional("執著執念")
tag=24
is_writer=true
craete_at=1521365431.49331
*/
複製程式碼
列印輸出說明這個方法成功了,o( ̄▽ ̄)d
看起來問題解決了,實際不然,因為我們上面的 UserModel 是沒有子類的,所以沒問題,但如果我們再建立一個 WriterModel,繼承自 UserModel,則會發現 WriterModel 是沒有進行歸檔解檔的【(´⊙ω⊙`)一臉懵逼】,這是因為反射的特性是隻反射當前類名下的屬性,父類屬性需要呼叫 superMirror 屬性獲取,所以我們改造一下程式碼:
class UserModel: BasicCodingModel {
var name: String?
var age: Int = 0
var isWriter: Bool = false
var createAt: Double = 0
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class WriterModel: UserModel {
var writerName: String?
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class BasicCodingModel: NSObject, NSCoding {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
// 這個要先呼叫 super.init ,因為 decodeMirror 裡用到了 self,否則會報錯
super.init()
decodeMirror(coder: aDecoder)
}
func encode(with aCoder: NSCoder) {
encodeMirror(coder: aCoder)
}
// 用來確保獲取和設定的 key 是一致的
fileprivate func getCodingKey(_ label: String) -> String {
// 這裡直接用屬性名當 key
return label
}
// 解碼的反射應用
fileprivate func decodeMirror(coder aDecoder: NSCoder) {
// 建立當前模型的反射
var mirror: Mirror? = Mirror(reflecting: self)
while mirror != nil {
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)var mirror: Mirror? = Mirror(reflecting: model)
mirror?.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
var decodeValue: Any?
if value is Bool {
decodeValue = aDecoder.decodeBool(forKey: decodeKey)
} else if value is Int {
decodeValue = aDecoder.decodeInteger(forKey: decodeKey)
} else if value is Double {
decodeValue = aDecoder.decodeDouble(forKey: decodeKey)
} else {
decodeValue = aDecoder.decodeObject(forKey: decodeKey)
}
if let decodeValue = decodeValue {
// 通過使用 KVC 的方式對屬性進行賦值
self.setValue(decodeValue, forKeyPath: label)
}
}
}
// 判斷有沒有父類,直到頂層類
mirror = mirror?.superclassMirror
}
}
// 編碼的反射應用
fileprivate func encodeMirror(coder aCoder: NSCoder) {
// 建立當前模型的反射
var mirror: Mirror? = Mirror(reflecting: self)
while mirror != nil {
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)
mirror?.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
if let valueBool = value as? Bool {
// 具體呼叫的是 encode(_ value: Bool, forKey key: String)
aCoder.encode(valueBool, forKey: decodeKey)
} else if let valueInt = value as? Int {
// 具體呼叫的是 encode(_ value: Int, forKey key: String)
aCoder.encode(valueInt, forKey: decodeKey)
} else if let valueDouble = value as? Double {
// 具體呼叫的是 encode(_ value: Double, forKey key: String)
aCoder.encode(valueDouble, forKey: decodeKey)
} else {
// 具體呼叫的是 encode(_ object: Any?, forKey key: String)
aCoder.encode(value, forKey: decodeKey)
}
}
}
// 判斷有沒有父類,直到頂層類
mirror = mirror?.superclassMirror
}
}
}
複製程式碼
4)回顧問題
這樣我的問題已經解決了,可以直接在專案中用了,但是這段程式碼的健壯性太差了,測試也只是測試了 4 種常用型別而已,但專案中屬性型別是各種各樣的,需要測試完全才能用於專案中,不測不知道,一測嚇一跳。。。發現上面的程式碼有很大的一些問題
上面的程式碼只能在 Swift 4 以下版本執行,Swift 4 需要在
class BasicCodingModel
前面加上關鍵字@objcMembers
,因為 Swift 4 中的一個對此有影響的改變是繼承 NSObject 的 swift class 不再預設全部 bridge 到 OC,為了清除冗餘程式碼,減小包大小。列舉型別儲存屬性使用 KVC 會導致程式奔潰,-_-||汗,目前沒找到什麼好的方法相容這個,所以列舉型別還是使用以前的方式(實際上我一般不存列舉,只存列舉 rawValue,列舉可以用計算屬性代替)。
encode 值為 nil 的屬性時,decode 的時候會出現
-[NSNull length]: unrecognized selector sent to instance
的崩潰
下面是我優化了的程式碼,目前使用起來沒什麼問題,如果有什麼問題可以給我提出,我及時改正:
class UserModel: BasicCodingModel {
var name: String?
var age: Int = 0
var isWriter: Bool = false
var createAt: Double = 0
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class WriterModel: UserModel {
enum WriterType {
case white
case black
}
var type: WriterType = .white
var writerName: String?
var object: UserModel?
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
@objcMembers class BasicCodingModel: NSObject, NSCoding {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
// 這個要先呼叫 super.init ,因為 decodeMirror 裡用到了 self,否則會報錯
super.init()
decodeMirror(coder: aDecoder)
}
func encode(with aCoder: NSCoder) {
encodeMirror(coder: aCoder)
}
// 過載該方法是為了防止使用 KVC 設定沒有的屬性時不至於直接崩潰
override func setValue(_ value: Any?, forUndefinedKey key: String) {
}
// 用來確保獲取和設定的 key 是一致的
fileprivate func getCodingKey(_ label: String) -> String {
// 這裡直接用屬性名當 key
return label
}
// 解碼的反射應用
fileprivate func decodeMirror(coder aDecoder: NSCoder) {
// 建立當前模型的反射
var mirror: Mirror? = Mirror(reflecting: self)
while mirror != nil {
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)var mirror: Mirror? = Mirror(reflecting: model)
mirror?.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
var decodeValue: Any?
if value is Bool {
decodeValue = aDecoder.decodeBool(forKey: decodeKey)
} else if value is Int {
decodeValue = aDecoder.decodeInteger(forKey: decodeKey)
} else if value is Double {
decodeValue = aDecoder.decodeDouble(forKey: decodeKey)
} else if value is String {
decodeValue = aDecoder.decodeObject(forKey: decodeKey)
} else if let displayStyle = Mirror(reflecting: value).displayStyle, displayStyle != .`enum` { // 過濾掉可選型別
decodeValue = aDecoder.decodeObject(forKey: decodeKey)
}
if let decodeValue = decodeValue, !(decodeValue is NSNull) {
// 通過使用 KVC 的方式對屬性進行賦值
self.setValue(decodeValue, forKeyPath: label)
}
}
}
// 判斷有沒有父類,直到頂層類
mirror = mirror?.superclassMirror
}
}
// 編碼的反射應用
fileprivate func encodeMirror(coder aCoder: NSCoder) {
// 建立當前模型的反射
var mirror: Mirror? = Mirror(reflecting: self)
while mirror != nil {
// mirror.children 表示該模型所有儲存屬性集合,它是一個元組(label = 屬性名, value = 屬性值)
mirror?.children.forEach { (label, value) in
if let label = label {
let decodeKey = getCodingKey(label)
if let valueBool = value as? Bool {
// 具體呼叫的是 encode(_ value: Bool, forKey key: String)
aCoder.encode(valueBool, forKey: decodeKey)
} else if let valueInt = value as? Int {
// 具體呼叫的是 encode(_ value: Int, forKey key: String)
aCoder.encode(valueInt, forKey: decodeKey)
} else if let valueDouble = value as? Double {
// 具體呼叫的是 encode(_ value: Double, forKey key: String)
aCoder.encode(valueDouble, forKey: decodeKey)
} else if let valueStr = value as? String {
// 具體呼叫的是 encode(_ value: Any?, forKey key: String)
aCoder.encode(valueStr, forKey: decodeKey)
} else if let displayStyle = Mirror(reflecting: value).displayStyle, displayStyle != .`enum` { // 過濾掉可選型別
// 具體呼叫的是 encode(_ value: Any?, forKey key: String)
aCoder.encode(value, forKey: decodeKey)
}
}
}
// 判斷有沒有父類,直到頂層類
mirror = mirror?.superclassMirror
}
}
}
複製程式碼
以上就是我現在專案中使用到的一個簡單反射應用框架,用於歸檔和解檔還是挺方便的,比之前的繁瑣工作好太多了O(∩_∩)O哈哈~,如果你們用了我的這個程式碼發現了一些問題,可以及時反饋給我,我也會同步更新的。
這個程式碼地址:github 地址的 ArchiveWithMirror