Swift Codable實踐

背景:項目原本是使用HandyJson來做的數(shù)據(jù)-模型的相互轉(zhuǎn)換怜姿,后面測試發(fā)現(xiàn)在iOS12.1系統(tǒng)的,HandyJson庫會必現(xiàn)崩潰libswiftCoreGraphics image not found,該庫作者已經(jīng)不再更新且不推薦使用了,具體詳細原及可以看下我之前的文章:http://www.reibang.com/p/167167f54a0d

后面就考慮移除HandyJson改為系統(tǒng)Codable去實現(xiàn)數(shù)據(jù)-模型的相互轉(zhuǎn)換强衡,從Handyjson遷移到Codable的過程中竖幔,我發(fā)現(xiàn)了如下問題

  1. 我發(fā)現(xiàn)HandyJson在做轉(zhuǎn)換時,如果json數(shù)據(jù)的某個字段的類型與model類的對應(yīng)的字段類型不匹配時砸脊,HandyJson會嘗試自動幫你進行類型轉(zhuǎn)換具篇,但是在Codable中,類型不匹配會導(dǎo)致錯誤凌埂,換句話說就是必須保證jsonObjct中對應(yīng)的屬性名稱是對應(yīng)的驱显,否則該屬性的自動會轉(zhuǎn)換失敗,如果想要轉(zhuǎn)換瞳抓,必須手動實現(xiàn)codable對應(yīng)的方法埃疫。

  2. 對于繼承類型,無法自動轉(zhuǎn)換該類型的父類或子類屬性孩哑,如果想要轉(zhuǎn)換栓霜,必須手動實現(xiàn)codable對應(yīng)的方法。

  3. 系統(tǒng)對于實體類屬性,會根據(jù)是可選值或不可選值自動調(diào)用對應(yīng)的方法,如果json這個key為空,但是實體類屬性為不可選值,這也會導(dǎo)致對象轉(zhuǎn)換失敗横蜒。

這幾點差異胳蛮,導(dǎo)致對于每個改動的接口销凑,都需要進行一番調(diào)試,查看轉(zhuǎn)換是否正常仅炊,耗費了很多時間斗幼。

以下為實踐過程中總結(jié)的一些經(jīng)驗:
// 遵循codable協(xié)議,即可自動轉(zhuǎn)化,前提:
// 1.類的父類是NSobject或空
// 2.類的屬性類型與json或者jsonObjct中對應(yīng)的類型是對應(yīng)的,否則對象會轉(zhuǎn)化失敗
// 3.類的屬性名稱與json或者jsonObjct中對應(yīng)的屬性名稱是對應(yīng)的,否則該屬性會轉(zhuǎn)換失敗
// 4.如果包含嵌套類型,則嵌套類型也需要實現(xiàn)codable協(xié)議
// 5.系統(tǒng)對于實體類屬性,會根據(jù)是可選值或不可選值自動調(diào)用對應(yīng)的方法,如果json這個key為空,但是實體類屬性為不可選值,這也會導(dǎo)致對象轉(zhuǎn)換失敗

self.childName = try container.decodeIfPresent(String.self, forKey: .childName)
self.childName = try container.decode(String.self, forKey: .childName)
try cotainer.encodeIfPresent(self.childName, forKey: .childName)
try cotainer.encode(self.childName, forKey: .childName)

// 無法自動轉(zhuǎn)化時
// 1.類如果是子類,需要手動實現(xiàn)enum CodingKeys:/ init(from decoder: Decoder)/ encode(to encoder: Encoder)方法,父類需要遵守codable協(xié)議,但可以自動轉(zhuǎn)化
// 2.類的屬性類型不對應(yīng),需要手動實現(xiàn)enum CodingKeys:/ init(from decoder: Decoder)/ encode(to encoder: Encoder)方法
// 3.類的屬性名稱不對應(yīng),需要手動實現(xiàn)enum CodingKeys,所有屬性都必須寫出來

public class JsonToModelViewController: UIViewController {
    
    /*
    let jsonStr = """
            {
                "token_expires_timestamp":1680680304281,
                "uid":"4478366175",
                "token_expires_time":86400,
                "isSetPwd":"0",
                "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODA2ODAzMDQyODEsInBheWxvYWQiOiJ7XCJpbWlVaWRcIjpcIjQ0NzgzNjYxNzVcIixcImZsYWdcIjoxfSJ9.KOBDBhs_pnDPdp85UOebQ6lfJy_Tf_w-YhXj0KlTyok",
                "refresh_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODE4MDM1MDQyODEsInBheWxvYWQiOiJ7XCJpbWlVaWRcIjpcIjQ0NzgzNjYxNzVcIixcImZsYWdcIjoyfSJ9.0AJs5VFJjIOfOG3VjoJ9zx75taRS2xFGbrpEamyWoAk",
                "refresh_token_expires_timestamp":1681803504281,
                "authCode":"0167247243702923",
                "refresh_token_expires_time":1209600,
                "currentUtcDate":1680593904281,
            }
    """
     */
    let jsonStr = """
    {
            "name": "張三",
            "age": 20,
            "description": "A handsome boy.",
            "childName": "小a"
        }
    """
    let jsonLabel = UILabel(frame: CGRectZero)
    let jsonToModelButton = UIButton(type: .system)
    let modelToJsonButton = UIButton(type: .system)
    let modelToJsonObjButton = UIButton(type: .system)
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        initViews()
    }
    
    func initViews() {
        self.navigationItem.title = "json轉(zhuǎn)模型"
        self.view.backgroundColor = .white
        
        jsonLabel.text = jsonStr
        jsonLabel.numberOfLines = 0
        jsonLabel.adjustsFontSizeToFitWidth = true
        self.view.addSubview(jsonLabel)
        
        jsonToModelButton.setTitle("json轉(zhuǎn)model", for: .normal)
        jsonToModelButton.backgroundColor = .gray
        jsonToModelButton.addTarget(self, action: #selector(jsonToModel), for: .touchUpInside)
        self.view.addSubview(jsonToModelButton)
        
        modelToJsonButton.setTitle("model轉(zhuǎn)json", for: .normal)
        modelToJsonButton.backgroundColor = .gray
        modelToJsonButton.addTarget(self, action: #selector(modelToJson), for: .touchUpInside)
        self.view.addSubview(modelToJsonButton)
        
        modelToJsonObjButton.setTitle("model轉(zhuǎn)jsonObj", for: .normal)
        modelToJsonObjButton.backgroundColor = .gray
        modelToJsonObjButton.addTarget(self, action: #selector(modelToJsonObj), for: .touchUpInside)
        self.view.addSubview(modelToJsonObjButton)
    }
    
    public override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        jsonLabel.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(10)
            make.right.equalToSuperview().offset(-10)
            make.top.equalToSuperview().offset(64)
            make.height.lessThanOrEqualTo(300)
        }
        
        jsonToModelButton.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(10)
            make.right.equalToSuperview().offset(-10)
            make.top.equalTo(jsonLabel.snp.bottom).offset(10)
            make.height.equalTo(44)
        }
        
        modelToJsonButton.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(10)
            make.right.equalToSuperview().offset(-10)
            make.top.equalTo(jsonToModelButton.snp.bottom).offset(10)
            make.height.equalTo(44)
        }
        
        modelToJsonObjButton.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(10)
            make.right.equalToSuperview().offset(-10)
            make.top.equalTo(modelToJsonButton.snp.bottom).offset(10)
            make.height.equalTo(44)
        }
    }
    
    @objc private func jsonToModel() {
        do {
//            let model = try JSONDecoder().decode(ILAuthInfo.self, from: jsonStr.data(using: .utf8)!)
//            let model = try JSONDecoder().decode(ILTestModel.self, from: jsonStr.data(using: .utf8)!)
            let model = try JSONDecoder().decode(ILTestChildModel.self, from: jsonStr.data(using: .utf8)!)
            print("轉(zhuǎn)換得到的模型:\(model)")
        }catch {
            imiLogE("轉(zhuǎn)換失敗:\(error)")
        }
        
        let jsonStr = """
        {"total":2,"cameraCount":1,"items":[{"name":"cm1"},{"name":"cm2"}]}
        """
        do {
            let model = try JSONDecoder().decode(ILHomeRoomModel.self, from: jsonStr.data(using: .utf8)!)
            print("轉(zhuǎn)換得到的模型:\(model)")
        }catch {
            imiLogE("轉(zhuǎn)換失敗:\(error)")
        }
    }
    
    @objc private func modelToJson() {
        let m1 = ILTestModel()
        m1.name = "張三"
        let m2 = ILTestModel()
        m2.name = "李四"
        do {
            let jsonData = try JSONEncoder().encode([m1, m2])
            let json = String(data: jsonData, encoding: .utf8)
            print("轉(zhuǎn)換得到的json1:\(json ?? "空")")
        }catch{
            imiLogE("轉(zhuǎn)換失敗:\(error)")
        }
        
        let m = ILHomeRoomModel()
        m.total = 2
        m.cameraCount = 1
        let cm1 = ILRoomItem()
        cm1.name = "cm1"
        let cm2 = ILRoomItem()
        cm2.name = "cm2"
        m.items = [cm1, cm2]
        do {
            let jsonData = try JSONEncoder().encode(m)
            let json = String(data: jsonData, encoding: .utf8)
            print("轉(zhuǎn)換得到的json2:\(json ?? "空")")
        }catch{
            imiLogE("轉(zhuǎn)換失敗:\(error)")
        }
        
        let bm = ILTestBigModel()
        bm.bigName = "大名"
        bm.child = m2
        do {
            let jsonData = try JSONEncoder().encode(bm)
            let json = String(data: jsonData, encoding: .utf8)
            print("轉(zhuǎn)換得到的json3:\(json ?? "空")")
        }catch{
            imiLogE("轉(zhuǎn)換失敗:\(error)")
        }
    }
    
    @objc private func modelToJsonObj() {
        let model = ILTestModel()
        model.name = "李四"
        model.smallName = "a"
        model.des = "啊哈"
        model.age = 22
        
        let jsonObj = ILCodableUtil.modelToJsonObject(model) as? [String: Any]
        
    }
}

@objcMembers
public class ILTestModel: NSObject, Codable {
    var name: String?
    var smallName: String?
    var des: String?
    var age: Int = 0
    
    public override var description: String {
        "name:\(name ?? "空"), smallName:\(smallName ?? "空"), des:\(des ?? "空"), age:\(age)"
    }
    
//    enum CodingKeys: String, CodingKey {
//        case name
//        case smallName
//        case des = "description"
//        case age
//    }
    
    //手動實現(xiàn)
    /*
    public required init(from decoder: Decoder) throws {
        super.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.nickName = try container.decodeIfPresent(String.self, forKey: .nickName)
        self.deviceName = try container.decodeIfPresent(String.self, forKey: .deviceName)
        self.mac = try container.decodeIfPresent(String.self, forKey: .mac)
        self.productKey = try container.decodeIfPresent(String.self, forKey: .productKey)
        self.token = try container.decodeIfPresent(String.self, forKey: .token)
        self.categoryKey = try container.decodeIfPresent(String.self, forKey: .categoryKey)
        //特殊處理一下字典
        let dicData = try container.decodeIfPresent(Data.self, forKey: .extraDeviceInfo)
        self.extraDeviceInfo = ILLocalDeviceKeychainUtil.dataToDictionary(dicData);
    }

    public func encode(to encoder: Encoder) throws {
        //super.encode(to: encoder)
        var cotainer = encoder.container(keyedBy: CodingKeys.self)
        try cotainer.encodeIfPresent(self.nickName, forKey: .nickName)
        try cotainer.encodeIfPresent(self.deviceName, forKey: .deviceName)
        try cotainer.encodeIfPresent(self.mac, forKey: .mac)
        try cotainer.encodeIfPresent(self.productKey, forKey: .productKey)
        try cotainer.encodeIfPresent(self.token, forKey: .token)
        try cotainer.encodeIfPresent(self.categoryKey, forKey: .categoryKey)
        //特殊處理一下字典
        if let dicData = ILLocalDeviceKeychainUtil.dictionaryToData(self.extraDeviceInfo) {
            try cotainer.encodeIfPresent(dicData, forKey: .extraDeviceInfo)
        }
    }
     */
}

@objcMembers public class ILTestBigModel: NSObject, Codable {
    var bigName: String?
    var child: ILTestModel?
}

/// 子類想要使用Codable轉(zhuǎn)換,必須手寫以下實現(xiàn)
/// 與現(xiàn)有的 NSCoding API (NSKeyedArchiver) 不同,為了靈活性和安全性抚垄,新的 Swift 4 Codable 實現(xiàn)不會將有關(guān)編碼類型的類型信息寫入生成的檔案中蜕窿。因此,在解碼時呆馁,API 只能使用您提供的具體類型來解碼值(在您的情況下是超類類型)渠羞。
@objcMembers public class ILTestChildModel: ILTestModel {
    var childName: String?
    
    enum CodingKeys: String, CodingKey {
        case childName = "childName"
    }
    
    public required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.childName = try container.decodeIfPresent(String.self, forKey: .childName)
    }
    
    public override func encode(to encoder: Encoder) throws {
        var cotainer = encoder.container(keyedBy: CodingKeys.self)
        try cotainer.encodeIfPresent(self.childName, forKey: .childName)
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市智哀,隨后出現(xiàn)的幾起案子次询,更是在濱河造成了極大的恐慌,老刑警劉巖瓷叫,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屯吊,死亡現(xiàn)場離奇詭異,居然都是意外死亡摹菠,警方通過查閱死者的電腦和手機盒卸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來次氨,“玉大人蔽介,你說我怎么就攤上這事≈蠊眩” “怎么了虹蓄?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幸撕。 經(jīng)常有香客問我薇组,道長,這世上最難降的妖魔是什么坐儿? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任律胀,我火速辦了婚禮,結(jié)果婚禮上貌矿,老公的妹妹穿的比我還像新娘炭菌。我一直安慰自己,他們只是感情好逛漫,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布黑低。 她就那樣靜靜地躺著,像睡著了一般尽楔。 火紅的嫁衣襯著肌膚如雪投储。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天阔馋,我揣著相機與錄音玛荞,去河邊找鬼。 笑死呕寝,一個胖子當(dāng)著我的面吹牛勋眯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播下梢,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼客蹋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孽江?” 一聲冷哼從身側(cè)響起讶坯,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岗屏,沒想到半個月后辆琅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡这刷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年婉烟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暇屋。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡似袁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咐刨,到底是詐尸還是另有隱情昙衅,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布定鸟,位于F島的核電站绒尊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仔粥。R本人自食惡果不足惜婴谱,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躯泰。 院中可真熱鬧谭羔,春花似錦、人聲如沸麦向。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诵竭。三九已至话告,卻和暖如春兼搏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沙郭。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工佛呻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人病线。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓吓著,卻偏偏與公主長得像,于是被迫代替她去往敵國和親送挑。 傳聞我的和親對象是個殘疾皇子绑莺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容