准备条件
父类 Biology
Biology.h
1 #import <Foundation/Foundation.h> 2 3 @interface Biology : NSObject 4 { 5 NSInteger *_hairCountInBiology; 6 } 7 8 @property (nonatomic, copy) NSString *introInBiology; 9 10 @end
Biology.m
#import "Biology.h" @implementation Biology @end
子类 Person
Person.h
1 #import "Biology.h" 2 #import "Biology.h" 3 #import <objc/runtime.h> 4 5 @interface Person : Biology 6 { 7 NSString *_father; 8 } 9 10 @property (nonatomic, copy) NSString *name; 11 @property (nonatomic, assign) NSInteger age; 12 13 @end
Person.m
#import "Person.h" @implementation Person @end
在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档。
在序列化实现中不可避免的需要实现NSCoding以及NSCopying(非必须)协议的以下方法:
1 - (id)initWithCoder:(NSCoder *)coder; 2 - (void)encodeWithCoder:(NSCoder *)coder; 3 - (id)copyWithZone:(NSZone *)zone;
对Person类进行序列化,代码是这样:
1 //对变量编码 2 - (void)encodeWithCoder:(NSCoder *)coder 3 { 4 [coder encodeObject:self.name forKey:@"name"]; 5 [coder encodeObject:@(self.age) forKey:@"age"]; 6 [coder encodeObject:_father forKey:@"_father"]; 7 //... ... other instance variables 8 } 9 10 11 //对变量解码 12 - (id)initWithCoder:(NSCoder *)coder 13 { 14 self.name = [coder decodeObjectForKey:@"name"]; 15 self.age = [[coder decodeObjectForKey:@"age"] integerValue]; 16 _father = [coder decodeObjectForKey:@"_father"]; 17 //... ... other instance variables 18 }
但是请考虑以下问题:
a. 若Person是个很大的类,有非常多的变量需要进行encode/decode处理呢?
b. 若你的工程中有很多像Person的自定义类需要做序列化操作呢?
c. 若Person不是直接继承自NSObject而是有多层的父类呢?(请注意,序列化的原则是所有层级的父类的属性变量也要需要序列化【自己序列化,父类也要序列化,即使父类不需要】);
如果采用开始的传统的序列化方式进行序列化,在碰到以上问题时容易暴露出以下缺陷(仅仅是缺陷,不能称为问题):
a. 工程代码中冗余代码很多
b. 父类层级复杂容易导致遗漏点一些父类中的属性变量
那是不是有更优雅的方案来回避以上问题呢?那是必须的。这里我们将共同探讨使用runtime来实现一种接口简洁并且十分通用的iOS序列化与反序列方案。
Runtime 序列化和反序列化
由 initWithCoder
代码我们可以发现,序列化与反序列化中最重要的环节是遍历类的变量,保证不能遗漏。
注意:编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
由此可见,我们会写许多无聊的代码。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。
runtime 获取变量和属性
runtime中获取某类的所有变量(属性变量以及实例变量)API:
1 Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
获取某类的所有属性变量API:
1 objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
runtime的所有开放API都放在【objc/runtime.h】
里面,可以按住 Command 然后鼠标点击 它,进入。
Ivar是runtime对于变量的定义,本质是一个结构体:
1 struct objc_ivar { 2 char *ivar_name; 3 char *ivar_type; 4 int ivar_offset; 5 #ifdef __LP64__ 6 int space; 7 #endif 8 } 9 10 typedef struct objc_ivar *Ivar;
- ivar_name:变量名,对于一个给定的Ivar,可以通过
const char *ivar_getName(Ivar v)
函数获得char *
类型的变量名;
- ivar_type: 变量类型,在runtime中变量类型用字符串表示,用@表示 id 类型,用i表示int类型...。这不在本文讨论之列。类似地,可以通过
const char *ivar_getTypeEncoding(Ivar v)
函数获得变量类型;
- ivar_offset: 基地址偏移字节数,可以不用理会。
获取所有变量的代码
unsigned int numIvars; //成员变量个数 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)]; //获取成员变量的数据类型 NSLog(@"variable type :%@", key); } free(vars);//记得释放掉
获取类的属性变量的代码
1 unsigned int outCount, i; 2 3 objc_property_t *properties = class_copyPropertyList([self class], &outCount); 4 for (i = 0; i < outCount; i++) { 5 objc_property_t property = properties[i]; 6 NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] ; 7 NSLog(@"property name:%@", propertyName); 8 } 9 10 free(properties);
【objc_property_t】是runtime对于属性变量的定义,本质上也是一个结构体(事实上OC是对C的封装,大多数类型的本质都是C结构体)。在runtime.h
头文件中只有【typedef struct objc_property *objc_property_t】
,并没有更详细的结构体介绍。
进入正题,用Runtime实现序列化和反序列化
可以在【initWithCoder:】
以及【encoderWithCoder:】
中遍历类的所有变量,取得变量名作为KEY值,最后使用KVC强制取得或者赋值给对象。
代码下:
1 - (id)initWithCoder:(NSCoder* )coder 2 { 3 unsigned int iVarCount = 0; 4 Ivar *iVarList = class_copyIvarList([self class], &iVarCount);//取得变量列表,[self class]表示对自身类进行操作 5 for (int i = 0; i < iVarCount; i++) { 6 Ivar var = *(iVarList + i); 7 const char varName = *ivar_getName(var);//取得变量名字,将作为key 8 NSString *key = [NSString stringWithUTF8String:&varName]; 9 //decode 10 id value = [coder decodeObjectForKey:key];//解码 11 if (value) { 12 [self setValue:value forKey:key];//使用KVC强制写入到对象中 13 } 14 } 15 free(iVarList);//记得释放内存 16 return self; 17 } 18 19 //编码 20 - (void)encodeWithCoder:(NSCoder* )coder 21 { 22 unsigned int varCount = 0; 23 Ivar *ivarList = class_copyIvarList([self class], &varCount); 24 for (int i = 0; i < varCount; i++) { 25 Ivar var = *(ivarList + i); 26 const char *varName = ivar_getName(var); 27 NSString *key = [NSString stringWithUTF8String:varName]; 28 id varValue = [self valueForKey:key];//使用KVC获取key对应的变量值 29 if (varValue) { 30 [coder encodeObject:varValue forKey:key]; 31 } 32 } 33 free(ivarList); 34 }
上面代码有一个缺陷,在获取变量时都是指定当前类,也就是
[self class]。当你的Model对象并不是直接继承自NSObject时容易遗漏掉父类的属性。请注意我们一直提到的:
编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
因此在上面代码的基础上我们我们需要改进一下,设一个指针,先指向本身类,处理完指向SuperClass,处理完再指向SuperClass的SuperClass...。代码如下(这里仅以encodeWithCoder:
为例,毕竟initWithCoder:
同理):
1 //编码 2 - (void)encodeWithCoder:(NSCoder *)coder 3 { 4 Class cls = [self class]; 5 while (cls != [NSObject class]) {//对NSObject的变量不做处理 6 unsigned int iVarCount = 0; 7 Ivar *ivarList = class_copyIvarList([cls class], &iVarCount);/*变量列表,含属性以及私有变量*/ 8 for (int i = 0; i < iVarCount; i++) { 9 const char *varName = ivar_getName(*(ivarList + i)); 10 NSString *key = [NSString stringWithUTF8String:varName]; 11 /*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/ 12 id varValue = [self valueForKey:key]; 13 if (varValue) { 14 [coder encodeObject:varValue forKey:key]; 15 } 16 } 17 free(ivarList); 18 cls = class_getSuperclass(cls); //指针指向当前类的父类 19 } 20 }
这样真的结束了吗?不是的。当你运行上面代码时有可能会crash掉,crash的地方在 [self objectForKey:key]
这一句上。原来是这里的KVC无法获取到父类的私有变量(即实例变量)。因此,在处理到父类时不能简单粗暴地使用【class_copyIvarList】
,而只能取父类的属性变量。这时的【class_copyPropertyList】
就派上用场了。在处理父类时用后者代替前者。于是最终的代码(嗯**其实还不算最终):
1 - (id)initWithCoder:(NSCoder *)coder 2 { 3 NSLog(@"%s",__func__); 4 Class cls = [self class]; 5 while (cls != [NSObject class]) { 6 /*判断是自身类还是父类*/ 7 BOOL bIsSelfClass = (cls == [self class]); 8 unsigned int iVarCount = 0; 9 unsigned int propVarCount = 0; 10 unsigned int sharedVarCount = 0; 11 Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/ 12 objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/ 13 sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; 14 15 for (int i = 0; i < sharedVarCount; i++) { 16 const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); 17 NSString *key = [NSString stringWithUTF8String:varName]; 18 id varValue = [coder decodeObjectForKey:key]; 19 if (varValue) { 20 [self setValue:varValue forKey:key]; 21 } 22 } 23 free(ivarList); 24 free(propList); 25 cls = class_getSuperclass(cls); 26 } 27 return self; 28 } 29 30 31 32 - (void)encodeWithCoder:(NSCoder *)coder 33 { 34 NSLog(@"%s",__func__); 35 Class cls = [self class]; 36 while (cls != [NSObject class]) { 37 /*判断是自身类还是父类*/ 38 BOOL bIsSelfClass = (cls == [self class]); 39 unsigned int iVarCount = 0; 40 unsigned int propVarCount = 0; 41 unsigned int sharedVarCount = 0; 42 Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/ 43 objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/ 44 sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; 45 46 for (int i = 0; i < sharedVarCount; i++) { 47 const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); 48 NSString *key = [NSString stringWithUTF8String:varName]; 49 /*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/ 50 id varValue = [self valueForKey:key]; 51 if (varValue) { 52 [coder encodeObject:varValue forKey:key]; 53 } 54 } 55 free(ivarList); 56 free(propList); 57 cls = class_getSuperclass(cls); 58 } 59 }
在 ViewController.m 中分别调用这两个方法:
1 - (NSString *)filePath 2 { 3 NSString *archiverFilePath = [NSString stringWithFormat:@"%@/archiver", NSHomeDirectory()]; 4 return archiverFilePath; 5 }
归档:
1 - (void) archiveFunction { 2 Person *person = [[Person alloc] init]; 3 person.name = @"王者荣耀--> 荆轲"; 4 person.age = 23; 5 [person setValue:@"Harely Created me" forKey:@"_father"]; 6 person.introInBiology = @"我最终来自与 Biology"; 7 NSLog(@"Before archiver: %@", [person description]); 8 9 NSMutableData *data = [NSMutableData data]; 10 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 11 [archiver encodeObject:person forKey: @"Person"]; 12 [archiver finishEncoding]; 13 [data writeToFile:[self filePath] atomically:YES]; 14 }
解档:
1 - (void) solutionFileFunction { 2 Person *thePerson = nil; 3 NSMutableData *dedata = [NSMutableData dataWithContentsOfFile:[self filePath]]; 4 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:dedata]; 5 thePerson = [unarchiver decodeObjectForKey:@"Person"]; 6 [unarchiver finishDecoding]; 7 8 NSLog(@"----->thePerson: name: %@, agee: %ld, introInBiology: %@, _father: %@", thePerson.name, (long)thePerson.age, thePerson.introInBiology, [thePerson valueForKey:@"_father"]); 9 }
打印结果:
1 2018-03-25 15:32:58.744385+0800 RAC[6417:888777] ----->thePerson: name: 王者荣耀--> 荆轲, agee: 23, introInBiology: 我最终来自与 Biology, _father: Harely Created you
一行代码的事
这里我们采用宏的方式将上述代码浓缩成一行,宏定义的 .pch 文件中
具体的代码在:Harely's GitHub