聲明:本文是本人 [編程小翁] 原創(chuàng),轉(zhuǎn)載請(qǐng)注明。
一、變量聲明
為便于下文討論,提前創(chuàng)建父類Biology
以及子類Person
:
Biology:
@interface Biology : NSObject
{
NSInteger *_hairCountInBiology;
}
@property (nonatomic, copy) NSString *introInBiology;
@end
@implementation Biology
@end
Person:
#import <Foundation/Foundation.h>
#import "Biology.h"
#import <objc/runtime.h>
@interface Person : Biology
{
NSString *_father;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
補(bǔ)充說(shuō)明
凡是在父類中定義的屬性或者變量,末尾都有InBiology標(biāo)志;反之也成立
二、問(wèn)題引入
在iOS中一個(gè)自定義對(duì)象是無(wú)法直接存入到文件中的,必須先轉(zhuǎn)化成二進(jìn)制流才行。從對(duì)象到二進(jìn)制數(shù)據(jù)的過(guò)程我們一般稱為對(duì)象的序列化(Serialization),也稱為歸檔(Archive)。同理,從二進(jìn)制數(shù)據(jù)到對(duì)象的過(guò)程一般稱為反序列化或者反歸檔。
在序列化實(shí)現(xiàn)中不可避免的需要實(shí)現(xiàn)NSCoding以及NSCopying(非必須)協(xié)議的以下方法:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
- (id)copyWithZone:(NSZone *)zone;
假設(shè)我們現(xiàn)在需要對(duì)直接繼承自NSObject的Person類進(jìn)行序列化,代碼一般長(zhǎng)這樣子:
//對(duì)變量編碼
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.name forKey:@"name"];
[coder encodeObject:@(self.age) forKey:@"age"];
[coder encodeObject:_father forKey:@"_father"];
//... ... other instance variables
}
//對(duì)變量解碼
- (id)initWithCoder:(NSCoder *)coder
{
self.name = [coder decodeObjectForKey:@"name"];
self.age = [[coder decodeObjectForKey:@"age"] integerValue];
_father = [coder decodeObjectForKey:@"_father"];
//... ... other instance variables
似乎so easy?至少到目前為止是這樣的。但是請(qǐng)考慮以下問(wèn)題:
- 若Person是個(gè)很大的類,有非常多的變量需要進(jìn)行encode/decode處理呢?
- 若你的工程中有很多像Person的自定義類需要做序列化操作呢?
- 若Person不是直接繼承自NSObject而是有多層的父類呢?(請(qǐng)注意,序列化的原則是所有層級(jí)的父類的屬性變量也要需要序列化);
如果采用開(kāi)始的傳統(tǒng)的序列化方式進(jìn)行序列化,在碰到以上問(wèn)題時(shí)容易暴露出以下缺陷(僅僅是缺陷,不能稱為問(wèn)題):
- 工程代碼中冗余代碼很多
- 父類層級(jí)復(fù)雜容易導(dǎo)致遺漏點(diǎn)一些父類中的屬性變量
那是不是有更優(yōu)雅的方案來(lái)回避以上問(wèn)題呢?那是必須的。這里我們將共同探討使用runtime來(lái)實(shí)現(xiàn)一種接口簡(jiǎn)潔并且十分通用的iOS序列化與反序列方案。
三、runtime: iOS序列化與反序列化利器
3.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)行編解碼。
3.2 使用runtime獲取變量以及屬性
runtime中獲取某類的所有變量(屬性變量以及實(shí)例變量)API:
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
獲取某類的所有屬性變量API:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *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)體:
struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
}
typedef struct objc_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(int i = 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)這樣子:
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = 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)制取得或者賦值給對(duì)象。于是我們可以得到如下的自動(dòng)序列化與發(fā)序列化代碼,關(guān)鍵部分有注釋:
@implementation Person
//解碼
- (id)initWithCoder:(NSCoder *)coder
{
unsigned int iVarCount = 0;
Ivar *iVarList = class_copyIvarList([self class], &iVarCount);//取得變量列表,[self class]表示對(duì)自身類進(jìn)行操作
for (int i = 0; i < iVarCount; i++) {
Ivar var = *(iVarList + i);
const char * varName = 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)制寫(xiě)入到對(duì)象中
}
}
free(iVarList);//記得釋放內(nèi)存
return self;
}
//編碼
- (void)encodeWithCoder:(NSCoder *)coder
{
unsigned int varCount = 0;
Ivar *ivarList = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i++) {
Ivar var = *(ivarList + i);
const char *varName = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:key];//使用KVC獲取key對(duì)應(yīng)的變量值
if (varValue) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
}
3.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 = [self class];
while (cls != [NSObject class]) {//對(duì)NSObject的變量不做處理
unsigned int iVarCount = 0;
Ivar *ivarList = class_copyIvarList([cls class], &iVarCount);/*變量列表,含屬性以及私有變量*/
for (int i = 0; i < iVarCount; i++) {
const char *varName = ivar_getName(*(ivarList + i));
NSString *key = [NSString stringWithUTF8String:varName];
/*valueForKey只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/
id varValue = [self valueForKey: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 = [self class];
while (cls != [NSObject class]) {
/*判斷是自身類還是父類*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = 0;
unsigned int propVarCount = 0;
unsigned int sharedVarCount = 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 (int i = 0; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [coder decodeObjectForKey:key];
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
if (varValue && [filters containsObject:key] == NO) {
[self setValue:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
NSLog(@"%s",__func__);
Class cls = [self class];
while (cls != [NSObject class]) {
/*判斷是自身類還是父類*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = 0;
unsigned int propVarCount = 0;
unsigned int sharedVarCount = 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 (int i = 0; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
/*valueForKey只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/
id varValue = [self valueForKey: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);
}
}
3.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 = [self class]; \
while (cls != [NSObject class]) { \
/*判斷是自身類還是父類*/ \
BOOL bIsSelfClass = (cls == [self class]); \
unsigned int iVarCount = 0; \
unsigned int propVarCount = 0; \
unsigned int sharedVarCount = 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 (int i = 0; i < sharedVarCount; i++) { \
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \
NSString *key = [NSString stringWithUTF8String:varName]; \
id varValue = [coder decodeObjectForKey:key]; \
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
if (varValue && [filters containsObject:key] == NO) { \
[self setValue:varValue forKey:key]; \
} \
} \
free(ivarList); \
free(propList); \
cls = class_getSuperclass(cls); \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)coder \
{ \
NSLog(@"%s",__func__); \
Class cls = [self class]; \
while (cls != [NSObject class]) { \
/*判斷是自身類還是父類*/ \
BOOL bIsSelfClass = (cls == [self class]); \
unsigned int iVarCount = 0; \
unsigned int propVarCount = 0; \
unsigned int sharedVarCount = 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 (int i = 0; i < sharedVarCount; i++) { \
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \
NSString *key = [NSString stringWithUTF8String:varName]; \
/*valueForKey只能獲取本類所有變量以及所有層級(jí)父類的屬性,不包含任何父類的私有變量(會(huì)崩潰)*/ \
id varValue = [self valueForKey: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.
- 喜歡本文可以點(diǎn)一下喜歡并關(guān)注我,或者留個(gè)言示個(gè)愛(ài)(拋媚眼中)
- 不喜歡可以留言提建議,我必虛心接受
- 歡迎轉(zhuǎn)載