(轉(zhuǎn))一行代碼實(shí)現(xiàn)iOS序列化與反序列化(runtime)

一碍论、變量聲明

為便于下文討論,提前創(chuàng)建父類Biology以及子類Person:

Biology:

@interfaceBiology:NSObject{NSInteger*_hairCountInBiology;}@property(nonatomic,copy)NSString*introInBiology;@end@implementationBiology@end

Person:

#import#import"Biology.h"#import@interfacePerson:Biology{NSString*_father;}@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)NSIntegerage;@end@implementationPerson@end

補(bǔ)充說明

凡是在父類中定義的屬性或者變量设捐,末尾都有InBiology標(biāo)志蓬衡;反之也成立

二、問題引入

在iOS中一個(gè)自定義對象是無法直接存入到文件中的晦款,必須先轉(zhuǎn)化成二進(jìn)制流才行规个。從對象到二進(jìn)制數(shù)據(jù)的過程我們一般稱為對象的序列化(Serialization)凤薛,也稱為歸檔(Archive)。同理绰姻,從二進(jìn)制數(shù)據(jù)到對象的過程一般稱為反序列化或者反歸檔枉侧。

在序列化實(shí)現(xiàn)中不可避免的需要實(shí)現(xiàn)NSCoding以及NSCopying(非必須)協(xié)議的以下方法:

- (id)initWithCoder:(NSCoder*)coder;- (void)encodeWithCoder:(NSCoder*)coder;- (id)copyWithZone:(NSZone*)zone;

假設(shè)我們現(xiàn)在需要對直接繼承自NSObject的Person類進(jìn)行序列化,代碼一般長這樣子:

//對變量編碼- (void)encodeWithCoder:(NSCoder *)coder{? ? [coderencodeObject:self.nameforKey:@"name"];? ? [coderencodeObject:@(self.age)forKey:@"age"];? ? [coderencodeObject:_fatherforKey:@"_father"];//... ... other instance variables}//對變量解碼- (id)initWithCoder:(NSCoder *)coder{? ? self.name = [coderdecodeObjectForKey:@"name"];? ? self.age = [[coderdecodeObjectForKey:@"age"] integerValue];? ? _father = [coderdecodeObjectForKey:@"_father"];//... ... other instance variables

似乎so easy狂芋?至少到目前為止是這樣的榨馁。但是請考慮以下問題:

若Person是個(gè)很大的類,有非常多的變量需要進(jìn)行encode/decode處理呢帜矾?

若你的工程中有很多像Person的自定義類需要做序列化操作呢翼虫?

若Person不是直接繼承自NSObject而是有多層的父類呢屑柔?(請注意,序列化的原則是所有層級的父類的屬性變量也要需要序列化)珍剑;

如果采用開始的傳統(tǒng)的序列化方式進(jìn)行序列化掸宛,在碰到以上問題時(shí)容易暴露出以下缺陷(僅僅是缺陷,不能稱為問題):

工程代碼中冗余代碼很多

父類層級復(fù)雜容易導(dǎo)致遺漏點(diǎn)一些父類中的屬性變量

那是不是有更優(yōu)雅的方案來回避以上問題呢招拙?那是必須的唧瘾。這里我們將共同探討使用runtime來實(shí)現(xiàn)一種接口簡潔并且十分通用的iOS序列化與反序列方案。

三别凤、runtime: iOS序列化與反序列化利器

3.1 總體思路

觀察上面的initWithCoder代碼我們可以發(fā)現(xiàn)饰序,序列化與反序列化中最重要的環(huán)節(jié)是遍歷類的變量,保證不能遺漏规哪。

這里需要特別注意的是:

編解碼的范圍不能僅僅是自身類的變量求豫,還應(yīng)當(dāng)把除NSObject類外的所有層級父類的屬性變量也進(jìn)行編解碼!

由此可見诉稍,這幾乎是個(gè)純體力活蝠嘉。而runtime在遍歷變量這件事情上能為我們提供什么幫助呢?我們可以通過runtime在運(yùn)行時(shí)獲取自身類的所有變量進(jìn)行編解碼杯巨;然后對父類進(jìn)行遞歸蚤告,獲取除NSObject外每個(gè)層級父類的屬性(非私有變量),進(jìn)行編解碼服爷。

3.2 使用runtime獲取變量以及屬性

runtime中獲取某類的所有變量(屬性變量以及實(shí)例變量)API:

Ivar *class_copyIvarList(Class cls,unsignedint*outCount)

獲取某類的所有屬性變量API:

objc_property_t*class_copyPropertyList(Class cls,unsignedint*outCount)

runtime的所有開放API都放在objc/runtime.h里面罩缴。上面的一些數(shù)據(jù)類型有些同學(xué)可能沒見過,這里我們先簡單地介紹一下层扶,更詳細(xì)的介紹請自行查閱其他資料,強(qiáng)烈建議打開

Ivar是runtime對于變量的定義烙荷,本質(zhì)是一個(gè)結(jié)構(gòu)體:

structobjc_ivar {char*ivar_name;char*ivar_type;intivar_offset;#ifdef__LP64__intspace;#endif}typedefstructobjc_ivar *Ivar;

ivar_name:變量名镜会,對于一個(gè)給定的Ivar,可以通過const char *ivar_getName(Ivar v)函數(shù)獲得char *類型的變量名终抽;

ivar_type: 變量類型戳表,在runtime中變量類型用字符串表示,例如用@表示id類型昼伴,用i表示int類型...匾旭。這不在本文討論之列。類似地圃郊,可以通過const char *ivar_getTypeEncoding(Ivar v)函數(shù)獲得變量類型价涝;

ivar_offset: 基地址偏移字節(jié)數(shù),可以不用理會

獲取所有變量的代碼一般長這樣子:

unsignedintnumIvars;//成員變量個(gè)數(shù)Ivar?*vars =?class_copyIvarList(NSClassFromString(@"UIView"), &numIvars);NSString*key=nil;for(inti =0; i < numIvars; i++) {? ? ? ??Ivar?thisIvar = vars[i];? ? ? ? key = [NSStringstringWithUTF8String:ivar_getName(thisIvar)];//獲取成員變量的名字NSLog(@"variable name :%@", key);? ? ? ? key = [NSStringstringWithUTF8String:ivar_getTypeEncoding(thisIvar)];//獲取成員變量的數(shù)據(jù)類型NSLog(@"variable?type :%@", key);? ? }? ??free(vars);//記得釋放掉

objc_property_t是runtime對于屬性變量的定義持舆,本質(zhì)上也是一個(gè)結(jié)構(gòu)體(事實(shí)上OC是對C的封裝色瘩,大多數(shù)類型的本質(zhì)都是C結(jié)構(gòu)體)伪窖。在runtime.h頭文件中只有typedef struct objc_property *objc_property_t,并沒有更詳細(xì)的結(jié)構(gòu)體介紹居兆。雖然runtime的源碼是開源的覆山,但這里并不打算深入介紹,這并不影響我們今天的主題泥栖。與Ivar的應(yīng)用同理簇宽,獲取類的屬性變量的代碼一般長這樣子:

unsignedintoutCount,?i;objc_property_t*properties?=?class_copyPropertyList([selfclass],?&outCount);for(i?=0;?i?<?outCount;?i++)?{objc_property_tproperty?=?properties[i];? ?????????NSString?*propertyName?=?[[[NSString?alloc]?initWithCString:property_getName(property)]?;? ?????????NSLog(@"property name:%@", propertyName);?????}free(properties);

3.3 用runtime實(shí)現(xiàn)序列化與反序列化

有了前面兩節(jié)的鋪墊,到這里自然就水到渠成了吧享。我們可以在initWithCoder:以及encoderWithCoder:中遍歷類的所有變量魏割,取得變量名作為KEY值,最后使用KVC強(qiáng)制取得或者賦值給對象耙蔑。于是我們可以得到如下的自動序列化與發(fā)序列化代碼见妒,關(guān)鍵部分有注釋:

```

@implementation Person

//解碼

(id)initWithCoder:(NSCoder)coder

{

unsigned int iVarCount = 0;

IvariVarList = class_copyIvarList([self class], &iVarCount);//取得變量列表,[self class]表示對自身類進(jìn)行操作

for (int i = 0; i < iVarCount; i++) {

Ivar var =(iVarList + i);

const charvarName = ivar_getName(var);//取得變量名字,將作為key

NSString *key = [NSString stringWithUTF8String:varName];

//decode

id value = [coder decodeObjectForKey:key];//解碼

if (value) {

[self setValue:value forKey:key];//使用KVC強(qiáng)制寫入到對象中

}

}

free(iVarList);//記得釋放內(nèi)存

return self;

}

//編碼

(void)encodeWithCoder:(NSCoder)coder

{

unsigned int varCount = 0;

IvarivarList = class_copyIvarList([self class], &varCount);

for (int i = 0; i < varCount; i++) {

Ivar var =(ivarList + i);

const charvarName = ivar_getName(var);

NSString *key = [NSString stringWithUTF8String:varName];

id varValue = [self valueForKey:key];//使用KVC獲取key對應(yīng)的變量值

if (varValue) {

[coder encodeObject:varValue forKey:key];

}

}

free(ivarList);

}

``###3.4 優(yōu)化 上面代碼有個(gè)缺陷甸陌,在獲取變量時(shí)都是指定當(dāng)前類须揣,也就是[self class]`。當(dāng)你的Model對象并不是直接繼承自NSObject時(shí)容易遺漏掉父類的屬性钱豁。請牢記3.1節(jié)我們提到的:

編解碼的范圍不能僅僅是自身類的變量耻卡,還應(yīng)當(dāng)把除NSObject類外的所有層級父類的屬性變量也進(jìn)行編解碼!

因此在上面代碼的基礎(chǔ)上我們我們需要注意一下細(xì)節(jié)牲尺,設(shè)一個(gè)指針卵酪,先指向本身類,處理完指向SuperClass谤碳,處理完再指向SuperClass的SuperClass...溃卡。代碼如下(這里僅以encodeWithCoder:為例,畢竟initWithCoder:同理):

- (void)encodeWithCoder:(NSCoder*)coder{ ? ? Class cls = [selfclass];while(cls != [NSObjectclass]) {//對NSObject的變量不做處理unsignedintiVarCount =0;? ? ? ? Ivar *ivarList = class_copyIvarList([cls class], &iVarCount);/*變量列表蜒简,含屬性以及私有變量*/for(inti =0; i < iVarCount; i++) {constchar*varName = ivar_getName(*(ivarList + i));NSString*key = [NSStringstringWithUTF8String:varName];/*valueForKey只能獲取本類所有變量以及所有層級父類的屬性瘸羡,不包含任何父類的私有變量(會崩潰)*/idvarValue = [selfvalueForKey:key];if(varValue) { ? ? ? ? ? ? ? ? [coder encodeObject:varValue forKey:key]; ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? ? free(ivarList); ? ? ? ? cls = class_getSuperclass(cls);//指針指向當(dāng)前類的父類} ? }

這樣真的結(jié)束了嗎?不是的搓茬。當(dāng)你的跑上面的代碼時(shí)程序有可能會crash掉犹赖,crash的地方在[self objectForKey:key]這一句上。原來是這里的KVC無法獲取到父類的私有變量(即實(shí)例變量)卷仑。因此峻村,在處理到父類時(shí)不能簡單粗暴地使用class_copyIvarList,而只能取父類的屬性變量锡凝。這時(shí)候3.2節(jié)部分的class_copyPropertyList就派上用場了粘昨。在處理父類時(shí)用后者代替前者。于是最終的代碼(額~其實(shí)還不算最終):

- (id)initWithCoder:(NSCoder*)coder? ? {NSLog(@"%s",__func__);? ? ? Class cls = [selfclass];while(cls != [NSObjectclass]) {/*判斷是自身類還是父類*/BOOLbIsSelfClass = (cls == [selfclass]);unsignedintiVarCount =0;unsignedintpropVarCount =0;unsignedintsharedVarCount =0;? ? ? ? ? ? Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) :NULL;/*變量列表,含屬性以及私有變量*/objc_property_t *propList = bIsSelfClass ?NULL: class_copyPropertyList(cls, &propVarCount);/*屬性列表*/sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;for(inti =0; i < sharedVarCount; i++) {constchar*varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));NSString*key = [NSStringstringWithUTF8String:varName];idvarValue = [coder decodeObjectForKey:key];if(varValue) { ? ? ? ? ? ? ? ? [selfsetValue:varValue forKey:key];? ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? ? free(ivarList); ? ? ? ? free(propList); ? ? ? ? cls = class_getSuperclass(cls); ? ? }returnself;? ? } ? - (void)encodeWithCoder:(NSCoder*)coder? ? {NSLog(@"%s",__func__);? ? ? Class cls = [selfclass];while(cls != [NSObjectclass]) {/*判斷是自身類還是父類*/BOOLbIsSelfClass = (cls == [selfclass]);unsignedintiVarCount =0;unsignedintpropVarCount =0;unsignedintsharedVarCount =0;? ? ? ? ? ? Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) :NULL;/*變量列表雾棺,含屬性以及私有變量*/objc_property_t *propList = bIsSelfClass ?NULL: class_copyPropertyList(cls, &propVarCount);/*屬性列表*/sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;for(inti =0; i < sharedVarCount; i++) {constchar*varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));NSString*key = [NSStringstringWithUTF8String:varName];/*valueForKey只能獲取本類所有變量以及所有層級父類的屬性膊夹,不包含任何父類的私有變量(會崩潰)*/idvarValue = [selfvalueForKey:key];if(varValue) { ? ? ? ? ? ? ? ? [coder encodeObject:varValue forKey:key]; ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? ? free(ivarList); ? ? ? ? free(propList); ? ? ? ? cls = class_getSuperclass(cls); ? ? } ? }

3.5 最終的封裝

在邏輯上,上面的代碼應(yīng)該是目前為止比較完美的自動序列化與反序列解決方案了捌浩。即使某個(gè)類的繼承深度極其深放刨,變量極其多,序列化的代碼也就以上這些尸饺。但是我們回到文章第二節(jié)提出的幾點(diǎn)場景假設(shè)进统,其中有一點(diǎn)提到:

若你的工程中有很多像Person的自定義類需要做序列化操作呢?

如果是在以上場景下浪听,每個(gè)Model類都需要寫一次上面的代碼螟碎。這在一定程度上也造成冗余了。同時(shí)迹栓,你也會覺得這篇文章的標(biāo)題就是瞎扯淡掉分,根本就不是一行代碼的事。上面的代碼冗余克伊,我這種對代碼有很強(qiáng)潔癖的程序旺是萬萬接受不了的酥郭。那就再封裝一層!這里我采用宏的方式(也可以放到某個(gè)工具類里面)將上述代碼濃縮成一行愿吹,放到一個(gè)叫WZLSerializeKit.h的頭文件中:

#define WZLSERIALIZE_CODER_DECODER() ? ? \\- (id)initWithCoder:(NSCoder*)coder? ? \{ ? \NSLog(@"%s",__func__);? \? ? Class cls = [selfclass]; ? \while(cls != [NSObjectclass]) { ? \/*判斷是自身類還是父類*/\BOOLbIsSelfClass = (cls == [selfclass]);? \unsignedintiVarCount =0; \unsignedintpropVarCount =0;? \unsignedintsharedVarCount =0;? ? \? ? ? ? Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) :NULL;/*變量列表不从,含屬性以及私有變量*/\? ? ? ? objc_property_t *propList = bIsSelfClass ?NULL: class_copyPropertyList(cls, &propVarCount);/*屬性列表*/\? ? ? ? sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; ? \? ? ? ? ? ? \for(inti =0; i < sharedVarCount; i++) {? \constchar*varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \NSString*key = [NSStringstringWithUTF8String:varName]; ? \idvarValue = [coder decodeObjectForKey:key]; ? \if(varValue) { \? ? ? ? ? ? ? ? [selfsetValue:varValue forKey:key];? ? \? ? ? ? ? ? } ? \? ? ? ? } ? \? ? ? ? free(ivarList); \? ? ? ? free(propList); \? ? ? ? cls = class_getSuperclass(cls); \? ? } ? \returnself;? ? \} ? \\- (void)encodeWithCoder:(NSCoder*)coder? ? \{ ? \NSLog(@"%s",__func__);? \? ? Class cls = [selfclass]; ? \while(cls != [NSObjectclass]) { ? \/*判斷是自身類還是父類*/\BOOLbIsSelfClass = (cls == [selfclass]);? \unsignedintiVarCount =0; \unsignedintpropVarCount =0;? \unsignedintsharedVarCount =0;? ? \? ? ? ? Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) :NULL;/*變量列表,含屬性以及私有變量*/\? ? ? ? objc_property_t *propList = bIsSelfClass ?NULL: class_copyPropertyList(cls, &propVarCount);/*屬性列表*/\? ? ? ? sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; ? \? ? ? ? \for(inti =0; i < sharedVarCount; i++) {? \constchar*varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \NSString*key = [NSStringstringWithUTF8String:varName];? ? \/*valueForKey只能獲取本類所有變量以及所有層級父類的屬性犁跪,不包含任何父類的私有變量(會崩潰)*/\idvarValue = [selfvalueForKey:key]; ? \if(varValue) { \? ? ? ? ? ? ? ? [coder encodeObject:varValue forKey:key]; ? \? ? ? ? ? ? } ? \? ? ? ? } ? \? ? ? ? free(ivarList); \? ? ? ? free(propList); \? ? ? ? cls = class_getSuperclass(cls); \? ? } ? \}

之后需要序列化的地方只要兩步:1椿息、import "WZLSerializeKit.h" 2、調(diào)用WZLSERIALIZE_CODER_DECODER();即可坷衍。兩個(gè)字:清爽寝优。

此外,copyWithZone中同樣可以用相同的原理對變量進(jìn)行自動化copy枫耳。同樣地倡勇,我們也可以用一個(gè)宏封裝掉copyWithZone方法。這里就不再贅述嘉涌。

值得一提的是,以上代碼我已經(jīng)放到我的Github中夸浅,并且提供了CocoaPods支持仑最。使用的時(shí)候只需要podWZLSerializeKit。點(diǎn) [此處][2] 跳轉(zhuǎn)到我的Github.

[2]:https://github.com/weng1250/WZLSerializeKit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帆喇,一起剝皮案震驚了整個(gè)濱河市警医,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖预皇,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侈玄,死亡現(xiàn)場離奇詭異,居然都是意外死亡吟温,警方通過查閱死者的電腦和手機(jī)序仙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲁豪,“玉大人潘悼,你說我怎么就攤上這事∨老穑” “怎么了治唤?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糙申。 經(jīng)常有香客問我宾添,道長,這世上最難降的妖魔是什么柜裸? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任缕陕,我火速辦了婚禮,結(jié)果婚禮上粘室,老公的妹妹穿的比我還像新娘榄檬。我一直安慰自己,他們只是感情好衔统,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布鹿榜。 她就那樣靜靜地躺著,像睡著了一般锦爵。 火紅的嫁衣襯著肌膚如雪舱殿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天险掀,我揣著相機(jī)與錄音沪袭,去河邊找鬼。 笑死樟氢,一個(gè)胖子當(dāng)著我的面吹牛冈绊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埠啃,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼死宣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碴开?” 一聲冷哼從身側(cè)響起毅该,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤博秫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后眶掌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挡育,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年朴爬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了即寒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寝殴,死狀恐怖蒿叠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚣常,我是刑警寧澤市咽,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站抵蚊,受9級特大地震影響施绎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贞绳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一谷醉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冈闭,春花似錦俱尼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耍休,卻和暖如春刃永,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羊精。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工斯够, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喧锦。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓读规,卻偏偏與公主長得像,于是被迫代替她去往敵國和親燃少。 傳聞我的和親對象是個(gè)殘疾皇子掖桦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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