Swift 專案總結 01 Swift 反射應用於模型歸檔

執著丶執念發表於2018-06-02

Swift 專案總結 01   Swift 反射應用於模型歸檔

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")
    }
}
複製程式碼

當這樣的模型越來越多,我們就要維護越來越多這樣的東西,這樣是沒有什麼意義的,所以我在想,怎麼樣才能不用給每個模型都加這些程式碼就能實現這個功能呢?或者怎麼才能簡化這些程式碼?

Swift 專案總結 01   Swift 反射應用於模型歸檔

2)分析問題

首先我先觀察這些程式碼,發現這些程式碼核心就只有 2 個,就是 decode 和 encode,decode 需要設定屬性的值,encode 需要獲取屬性的值,有點類似我們 objc 屬性的 get、set 方法,需要我們拿到該模型的所有儲存屬性的型別,判斷是哪種型別,呼叫哪種 decode 和 encode 方法。而要獲取該模型的所有儲存屬性的型別,我想到了 Mirror 反射(對應 Obj-C 的執行時 runtime )。

Swift 專案總結 01   Swift 反射應用於模型歸檔

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

Swift 專案總結 01   Swift 反射應用於模型歸檔

看起來問題解決了,實際不然,因為我們上面的 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

相關文章