iOS開發(fā)技巧系列---打造強大的BaseModel(篇二:讓Model實現(xiàn)自動歸檔)

本文是iOS開發(fā)技巧系列---打造強大的BaseModel中的篇二,第一篇文章請見此:
讓Model自我描述 纵苛,相對于讓Model實現(xiàn)自我描述,讓Model實現(xiàn)自動歸檔的難度大得多言津。我相信能夠好好看完這幾篇文章的人攻人,絕對是有大收獲的。另外悬槽,些文章不適合新手怀吻,只適合有一定有Swift開發(fā)經(jīng)驗的人。

2018年Swift4已經(jīng)發(fā)布初婆,現(xiàn)在需要更新這些文章了蓬坡,里面的代碼可能都跑不起了猿棉。所以我要修正這些代碼讓其跑起來。我把這些代碼都放在iOSDemo項目里
https://github.com/DuckDeck/iOSDemo

什么是iOS的歸檔

歸檔--NSKeyedArchiver渣窜,是iOS開發(fā)中基本的數(shù)據(jù)存儲方式之一铺根,和其他的數(shù)據(jù)存儲方式相比,歸檔不僅能夠存儲任意類型的數(shù)據(jù)乔宿,而且使用起來也很簡單位迂。歸檔能將數(shù)據(jù)處理成NSData的形式,所以很容易以文件的形式保存在APP的沙盒中详瑞,而解歸和歸檔相反掂林,它是將保存在APP沙盒的歸檔文件逆歸檔,轉(zhuǎn)換成歸檔前的狀態(tài)坝橡。

傳統(tǒng)的iOS歸檔方式

要想讓一個自定義對象可以使用歸檔泻帮,必須要讓其符合NSCoding協(xié)議,

public protocol NSCoding {
    public func encodeWithCoder(aCoder: NSCoder)
    public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
@end

上面的代碼是iOS中NSCoding協(xié)議的定義计寇。里面包含兩個方法锣杂,其中一個是構(gòu)造器。第一個方法

public func encodeWithCoder(aCoder: NSCoder)

就是歸檔方法番宁,它是為了告訴NSKeyedArchiver對象如何將數(shù)據(jù)歸檔成文件的元莫。第二個方法(構(gòu)造器)

public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER

就是解檔方法了。它是告訴NSKeyedUnArchiver是如何將歸檔好的對象解檔成原來的數(shù)據(jù)的
下面來看看傳統(tǒng)的iOS歸檔方式蝶押,先定義一個類踱蠢,讓其符合NSCoding協(xié)議

 @objcMembers class DemoArchiver:GrandModel,NSCoding {
    var demoString:String?
    var demoInt = 100
    var demoFloat:Float = 0.0
    var demoDate = Date()
    override init() { }
    
    func encode(with aCoder: NSCoder) {//歸檔需要實現(xiàn)的方法
        aCoder.encode(demoString, forKey: "demoString")
        aCoder.encode(demoInt, forKey: "demoInt")
        aCoder.encode(demoFloat, forKey: "demoFloat")
        aCoder.encode(demoDate, forKey: "demoDate")
    }
    
    @objc required init?(coder aDecoder: NSCoder) {//解檔需要實現(xiàn)的構(gòu)造器
        demoString = aDecoder.decodeObject(forKey: "demoString") as? String
        demoInt = aDecoder.decodeInteger(forKey: "demoInt")
        demoFloat = aDecoder.decodeFloat(forKey: "demoFloat")
        demoDate = aDecoder.decodeObject(forKey: "demoDate") as! Date //存在強制轉(zhuǎn)換情況
    }
}

我們需要在正確地重寫這兩個方法。這里面最需要注意的點有兩個棋电,一是不要把數(shù)據(jù)類型搞錯茎截。二是key名不要弄錯了。然后下面開始測試

     let demoTest = DemoArchiver()
     demoTest.demoString = "ABCDEFG"
     demoTest.demoFloat = 11.11
     print(demoTest)
     let a = NSKeyedArchiver.archivedDataWithRootObject(demoTest)
     let b = NSKeyedUnarchiver.unarchiveObjectWithData(a)
     print(b)
     //打印結(jié)果
     DemoArchiver:["demoInt": 100, "demoString": ABCDEFG, "demoFloat": 11.11, "demoDate": 2016-03-09 13:03:17 +0000]
Optional(DemoArchiver:["demoInt": 100, "demoString": ABCDEFG, "demoFloat": 11.11, "demoDate": 2016-03-09 13:03:17 +0000])

可見經(jīng)過歸檔再解檔后的數(shù)據(jù)又恢復(fù)了原樣赶盔。這里需要說明一下的是企锌,一般是需要把歸檔后的文件保存在APP的沙盒目錄內(nèi)的,需要使用時再取出來解檔于未。這里為了測試方便就不這么做了撕攒。

傳統(tǒng)的iOS歸檔方式的弊端

相信大家很容易看出使用傳統(tǒng)的iOS歸檔方式的不足之處,還是和以前一樣沉眶,需要寫太多的重復(fù)啰嗦代碼了打却。目前對于Objc語言來說,有一個代碼生成器(Accessorizer,見(http://www.kevincallahan.org/software/accessorizer.html))可以使用谎倔,只需要把所有屬性放進去柳击,就可以生成所有屬性的歸檔解檔方法。遺憾的是Swift目前還沒有這種工具可以用(或者有了但是我不知道)片习,2018年應(yīng)該有了捌肴,只是我不太想用這東西蹬叭。只有老實的讓每個Model符合NSCoding協(xié)議,再寫出每個屬性的歸檔&解檔方法状知。其中最讓人疼的是有些屬性還需要強制轉(zhuǎn)換秽五。而一般情況下一個項目的Model數(shù)都超過了兩位數(shù),雖然不一定每個Model都需要歸檔功能饥悴,但是如果一個類里面屬性太多的話坦喘,寫起來會讓人很郁悶的。

使用RunTime實現(xiàn)自動歸檔

如果讀者看了我先前的兩篇--打造強大的BaseModel文章西设,腦子了應(yīng)該可以很快構(gòu)思出使用RunTime和KVC來實現(xiàn)自動歸檔的思路瓣铣。先用RunTime獲取Model中所有屬性名,再用KVC獲取每一個屬性的值贷揽。再調(diào)用encodeWithCoder就能實現(xiàn)歸檔了棠笑。嗯,這種想法不錯禽绪,下面直接寫代碼吧蓖救。
還是和以前一樣,先寫一個返回該類所有屬性名的方法

   func getSelfProperty()->[String]{  //和description屬性一樣
        var selfProperties = [String]()
        var count:UInt32 =  0
        let vars = class_copyIvarList(type(of: self), &count)
        for i in 0..<count {
            let t = ivar_getName((vars?[Int(i)])!)
            if let n = NSString(cString: t!, encoding: String.Encoding.utf8.rawValue) as String?
            {
                selfProperties.append(n)
            }
        }
        free(vars)
        return selfProperties
    }

和先前一樣印屁,利用Objc運行時的一系列方法可以從該類獲取所有的屬性名循捺,下面是測試

@objcMembers class DemoArchiver:GrandModel {
    var demoString:String?
    var demoInt = 0
    var demoFloat:Float = 0.0
    var demoDate = NSDate()
    
    override init(){}
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
print(DemoArchiver().getSelfProperty())
//打印出**["demoString", "demoInt", "demoFloat", "demoDate"]**

下面來讓GrandModel實現(xiàn)NSCoding協(xié)議,注意库车,實現(xiàn)NSCoding協(xié)議不能使用extension巨柒,因為指定構(gòu)造器不能聲明在extension中

class GrandModel:NSObject,NSCoding{
    //歸檔方法
    func encode(with aCoder: NSCoder) {
        let item = type(of: self).init()
        let properties = item.getSelfProperty()
        for propertyName in properties{
            let value = self.value(forKey: propertyName)
            aCoder.encode(value, forKey: propertyName)
        }
    }
    
    //解檔方法
    required init?(coder aDecoder: NSCoder) {
        super.init()
        let item = type(of: self).init()
        let properties = item.getSelfProperty()
        for propertyName in properties{
            let value = aDecoder.decodeObject(forKey: propertyName)
            self.setValue(value, forKey: propertyName)
        }
    }
 }

沒想到這么快就寫好了樱拴,看起來也不難嘛柠衍,但是實際上這里這里存在一個顯而易見的問題,就是歸檔方法中需要根據(jù)屬性的類型調(diào)用不同的encode(屬性類型)方法晶乔,本文的第一個例子里很清楚珍坊,對于Int類型的屬性,需要調(diào)用aCoder.encodeInteger方法正罢,F(xiàn)loat和Double也不一樣阵漏。如果統(tǒng)一使用 aCoder.encodeObject方法,就會造成數(shù)據(jù)類型丟失翻具,

測試使用RunTime實現(xiàn)自動歸檔是否有效

這里可以測試一下履怯。還是用文章開頭的例子的哪個類,只不過需要去掉里面其他所有的方法只保留屬性,并且添加了一些屬性用來測試

class DemoArchiver:GrandModel {
    var demoString:String?
    var demoInt = 10
    var demoFloat:Float = 11.0
    var demoDouble:Double = 22.0
    var demoDate = NSDate()
    var demoRect = CGRect(x: 1, y: 1, width: 1, height: 1)
}
   let demoTest = DemoArchiver()
   demoTest.demoString = "ABCDEFG"
   demoTest.demoFloat = 11.11
   print(demoTest)
   let a = NSKeyedArchiver.archivedDataWithRootObject(demoTest)
   let b = NSKeyedUnarchiver.unarchiveObjectWithData(a)
   print(b)
   //打印結(jié)果為
   **DemoArchiver:["demoDouble": 22, "demoInt": 10, "demoRect": NSRect: {{1, 1}, {1, 1}}, "demoString": ABCDEFG, "demoFloat": 11.11, "demoDate": 2016-03-12 07:57:57 +0000]
Optional(DemoArchiver:["demoDouble": 22, "demoInt": 10, "demoRect": NSRect: {{1, 1}, {1, 1}}, "demoString": ABCDEFG, "demoFloat": 11.11, "demoDate": 2016-03-12 07:57:57 +0000])**

實際上測試結(jié)果出乎我意料之外裆泳,非常完美叹洲,所有屬性都成功地歸檔保存下來,解檔后數(shù)據(jù)沒有出現(xiàn)丟失的情況工禾。對此我的分析是:這一切都是KVC的功勞运提。因為KVC取出的屬性都是為AnyObject蝗柔?類型,那么歸檔也就可以很方便地調(diào)用aCoder.encodeObject這個方法民泵,所以數(shù)據(jù)以AnyObject類型保存癣丧。取出來時正好相反,用aDecoder.decodeObjectForKey這個角檔方法取出來的數(shù)據(jù)類型都是AnyObject栈妆?類型的胁编。然后KVC在組屬性賦值并不需要知道每個屬性是什么樣的數(shù)據(jù)類型,都可以正確地賦值鳞尔。難道事情就這樣解決了嗎掏呼?我們來看看下個測試用例

@objcMembers class DemoArchiver:GrandModel {
    var demoString:String? = ""
    var demoInt = 0
    var demoFloat:Float = 0.0
    var demoDate = NSDate()
    var demoRect = CGRect(x: 1, y: 1, width: 1, height: 1)
}
let demoTest = DemoArchiver()
demoTest.demoString = "ABCDEFG"
demoTest.demoFloat = 11.11
print(demoTest)
let a = NSKeyedArchiver.archivedData(withRootObject: demoTest)
let b = NSKeyedUnarchiver.unarchiveObject(with: a)
print("------------歸檔后的數(shù)據(jù)------------")
print(b)
   //打印結(jié)果為
   **DemoArchiver:["demoFloat": 11.11, "demoString": ABCDEFG, "demoInt": 0, "demoRect": NSRect: {{1, 1}, {1, 1}}, "demoDate": 2018-03-29 09:14:29 +0000]
------------歸檔后的數(shù)據(jù)------------
Optional(DemoArchiver:["demoFloat": 11.11, "demoString": ABCDEFG, "demoInt": 0, "demoRect": NSRect: {{1, 1}, {1, 1}}, "demoDate": 2018-03-29 09:14:29 +0000])**

結(jié)果比預(yù)料中好了很多,nil的屬性都可以正確打印出來铅檩。但是和以前一樣憎夷,demoFloat:Float?這個屬性又丟失了,這是很正常的昧旨,因為Objc不支持這種數(shù)據(jù)類型拾给。讀過我這系列文章的讀者都可以明白。

從打印結(jié)果可以看出兔沃,歸檔后的數(shù)據(jù)Unarchiver后和原來的是一樣的蒋得,說明GrandModel起到作用了。

那么如果屬性類型是其他對象乒疏,或者是Array和字典類型呢?自動歸檔還能正常工作嗎额衙?答案是肯定的,只要該對象(Array或者Dict里保存的對象)都繼承于GrandModel,都可以實現(xiàn)自動歸檔解檔怕吴。

@objcMembers class DemoArchiver:GrandModel {
    var demoString:String? = ""
    var demoInt = 0
    var demoFloat:Float = 0.0
    var demoDate = NSDate()
    var demoRect = CGRect(x: 1, y: 1, width: 1, height: 1)
    var demoClass:demoArc?
    var demoArray:[demoArc]?
    var demoDict:[String:demoArc]?
}


@objcMembers class demoArc:GrandModel {
    var daString:String? = "default"
    var daInt:Int = 0
}

//下面測試
    let demoTest = DemoArchiver()
    demoTest.demoFloat = 11.11
    demoTest.demoClass = demoArc()
    demoTest.demoClass?.daInt = 8
    demoTest.demoClass?.daString = "demoArc"
    let a1 = demoArc()
    let a2 = demoArc()
    a1.daString = "a1"
    a1.daInt = 1
    a2.daInt = 2
    a2.daString = "a2"
    demoTest.demoArray = [a1,a2]
    demoTest.demoDict  = ["demo1":a1,"demo2":a2]
    print(demoTest)
    let a = NSKeyedArchiver.archivedData(withRootObject: demoTest)
    let b = NSKeyedUnarchiver.unarchiveObject(with: a)
    print("------------歸檔后的數(shù)據(jù)------------")
    print(b)
        
        //打印結(jié)果
        **DemoArchiver:["demoRect": NSRect: {{1, 1}, {1, 1}}, "demoDate": 2018-03-29 09:31:59 +0000, "demoFloat": 11.11, "demoString": , "demoInt": 0, "demoArray": <_TtGCs23_ContiguousArrayStorageC12ConsoleSwift7demoArc_ 0x100f617a0>(
demoArc:["daInt": 1, "daString": a1],
demoArc:["daInt": 2, "daString": a2]
)
, "demoClass": demoArc:["daInt": 8, "daString": demoArc], "demoDict": {
    demo1 = "demoArc:[\"daInt\": 1, \"daString\": a1]";
    demo2 = "demoArc:[\"daInt\": 2, \"daString\": a2]";
}]
------------歸檔后的數(shù)據(jù)------------
Optional(DemoArchiver:["demoRect": NSRect: {{1, 1}, {1, 1}}, "demoDate": 2018-03-29 09:31:59 +0000, "demoFloat": 11.11, "demoString": , "demoInt": 0, "demoArray": <__NSArrayI 0x101851250>(
demoArc:["daInt": 1, "daString": a1],
demoArc:["daInt": 2, "daString": a2]
)
, "demoClass": demoArc:["daInt": 8, "daString": demoArc], "demoDict": {
    demo1 = "demoArc:[\"daInt\": 1, \"daString\": a1]";
    demo2 = "demoArc:[\"daInt\": 2, \"daString\": a2]";
}])**

結(jié)果完全符合預(yù)期窍侧。

總結(jié)

讓Model自動歸檔是iOS Runtime和KVC強大威力的又一次體現(xiàn)。這個組合就像一把鋒利的尖刀转绷,可以準確高效地解決問題伟件,避免寫很多重復(fù)的代碼。缺點就是效率比正常代碼要低一點议经,但是我認為這完全是可以接受的斧账。這三篇文章所有的相關(guān)代碼都可以在我的Github里面找到(https://github.com/DuckDeck/iOSDemo),你們讀者能給個Star.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市煞肾,隨后出現(xiàn)的幾起案子咧织,更是在濱河造成了極大的恐慌,老刑警劉巖籍救,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件习绢,死亡現(xiàn)場離奇詭異,居然都是意外死亡钧忽,警方通過查閱死者的電腦和手機毯炮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門逼肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桃煎,你說我怎么就攤上這事篮幢。” “怎么了为迈?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵三椿,是天一觀的道長。 經(jīng)常有香客問我葫辐,道長搜锰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任耿战,我火速辦了婚禮蛋叼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剂陡。我一直安慰自己狈涮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布鸭栖。 她就那樣靜靜地躺著歌馍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晕鹊。 梳的紋絲不亂的頭發(fā)上松却,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機與錄音溅话,去河邊找鬼晓锻。 笑死,一個胖子當(dāng)著我的面吹牛公荧,可吹牛的內(nèi)容都是我干的带射。 我是一名探鬼主播同规,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼循狰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了券勺?” 一聲冷哼從身側(cè)響起绪钥,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关炼,沒想到半個月后程腹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡儒拂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年寸潦,在試婚紗的時候發(fā)現(xiàn)自己被綠了色鸳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡见转,死狀恐怖命雀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斩箫,我是刑警寧澤吏砂,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站乘客,受9級特大地震影響狐血,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜易核,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一匈织、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牡直,春花似錦报亩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至花竞,卻和暖如春劲件,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背约急。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工零远, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厌蔽。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓牵辣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奴饮。 傳聞我的和親對象是個殘疾皇子纬向,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,167評論 30 470
  • 時光匆匆,歲月如梭戴卜。又是新年了逾条,今年的的新年還是和往年一樣熱鬧。從臘月28開始全家團聚祭拜灶王爺投剥,吃餃子和麻糖...
    一顆小苗閱讀 474評論 0 0
  • 12月2日师脂,周三,景芳,兩節(jié)古詩詞教學(xué)吃警,萬千思緒感悟糕篇。留給我印象最深的就是以下三點: 一、談“涵詠酌心、推敲” 談涵詠...
    晚起畫蛾眉閱讀 1,854評論 0 1
  • 每當(dāng)大地沉睡娩缰,人們鼾聲似起的時候,我總喜歡去淘寶瀏覽一番才能睡覺谒府,就跟大多數(shù)女人愛逛商場一樣拼坎,有癮,總也忍不住完疫。平...
    自由和人生閱讀 1,517評論 0 0