iOS 序列化與反序列化(runtime) 02

//聯(lián)系人:石虎QQ: 1224614774昵稱:嗡嘛呢叭咪哄

一、runtime: iOS序列化與反序列化利器

1.1 總體思路

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

這里需要特別注意的是:

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

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

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

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

Ivar *class_copyIvarList(Class cls,unsignedint*outCount)

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

objc_property_t*class_copyPropertyList(Class cls,unsignedint*outCount)

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

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

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

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

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

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

獲取所有變量的代碼一般長(zhǎng)這樣子:

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

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

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

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

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

@implementationPerson//解碼- (id)initWithCoder:(NSCoder*)coder{unsignedintiVarCount =0;? Ivar *iVarList = class_copyIvarList([selfclass], &iVarCount);//取得變量列表,[self class]表示對(duì)自身類進(jìn)行操作for(inti =0; i < iVarCount; i++) {? ? ? Ivar var = *(iVarList + i);constchar* varName = ivar_getName(var);//取得變量名字,將作為keyNSString*key = [NSStringstringWithUTF8String:varName];//decodeidvalue = [coder decodeObjectForKey:key];//解碼if(value) {? ? ? ? ? [selfsetValue:value forKey:key];//使用KVC強(qiáng)制寫(xiě)入到對(duì)象中}? }? free(iVarList);//記得釋放內(nèi)存returnself;}//編碼- (void)encodeWithCoder:(NSCoder*)coder? ? ? ? {unsignedintvarCount =0;? ? ? ? Ivar *ivarList = class_copyIvarList([selfclass], &varCount);for(inti =0; i < varCount; i++) {? ? ? ? ? ? Ivar var = *(ivarList + i);constchar*varName = ivar_getName(var);NSString*key = [NSStringstringWithUTF8String:varName];idvarValue = [selfvalueForKey:key];//使用KVC獲取key對(duì)應(yīng)的變量值if(varValue) {? ? ? ? ? ? ? ? [coder encodeObject:varValue forKey:key];? ? ? ? ? ? }? ? ? }? ? ? free(ivarList);? ? }

1.4 優(yōu)化

上面代碼有個(gè)缺陷,在獲取變量時(shí)都是指定當(dāng)前類,也就是[self class]。當(dāng)你的Model對(duì)象并不是直接繼承自NSObject時(shí)容易遺漏掉父類的屬性。請(qǐng)牢記3.1節(jié)我們提到的:

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

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

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

這樣真的結(jié)束了嗎?不是的。當(dāng)你的跑上面的代碼時(shí)程序有可能會(huì)crash掉,crash的地方在[self valueForKey:key]這一句上。原來(lái)是這里的KVC無(wú)法獲取到父類的私有變量(即實(shí)例變量)。因此,在處理到父類時(shí)不能簡(jiǎn)單粗暴地使用class_copyIvarList,而只能取父類的屬性變量。這時(shí)候3.2節(jié)部分的class_copyPropertyList就派上用場(chǎng)了。在處理父類時(shí)用后者代替前者。

2016.0804補(bǔ)充

在最近的iOS中打印propertyList會(huì)發(fā)現(xiàn)有superClass、description、debugDescription、hash等四個(gè)屬性。對(duì)這幾個(gè)屬性進(jìn)行encode操作會(huì)導(dǎo)致crash。因此在encode前需要屏蔽掉這些key

于是最終的代碼(額~其實(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([clsclass], &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];NSArray*filters = @[@"superclass",@"description",@"debugDescription",@"hash"];if(varValue && [filters containsObject:key] ==NO) {? ? ? ? ? ? ? ? [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([clsclass], &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只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/idvarValue = [selfvalueForKey:key];NSArray*filters = @[@"superclass",@"description",@"debugDescription",@"hash"];if(varValue && [filters containsObject:key] ==NO) {? ? ? ? ? ? ? ? [coder encodeObject:varValue forKey:key];? ? ? ? ? ? ? }? ? ? ? ? }? ? ? ? ? free(ivarList);? ? ? ? free(propList);? ? ? ? cls = class_getSuperclass(cls);? ? }? }

1.5 最終的封裝

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

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

如果是在以上場(chǎng)景下,每個(gè)Model類都需要寫(xiě)一次上面的代碼。這在一定程度上也造成冗余了。同時(shí),你也會(huì)覺(jué)得這篇文章的標(biāo)題就是瞎扯淡,根本就不是一行代碼的事。上面的代碼冗余,我這種對(duì)代碼有很強(qiáng)潔癖的程序旺是萬(wàn)萬(wàn)接受不了的。那就再封裝一層!這里我采用宏的方式將上述代碼濃縮成一行,放到一個(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([clsclass], &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];? \NSArray*filters = @[@"superclass",@"description",@"debugDescription",@"hash"]; \if(varValue && [filters containsObject:key] ==NO) { \? ? ? ? ? ? ? ? [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([clsclass], &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只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/\idvarValue = [selfvalueForKey:key];? \NSArray*filters = @[@"superclass",@"description",@"debugDescription",@"hash"]; \if(varValue && [filters containsObject:key] ==NO) { \? ? ? ? ? ? ? ? [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中同樣可以用相同的原理對(duì)變量進(jìn)行自動(dòng)化copy。同樣地,我們也可以用一個(gè)宏封裝掉copyWithZone方法。這里就不再贅述。

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

謝謝!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容