Runtime(IV)

准备条件

父类 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 }
View Code

但是请考虑以下问题:

  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 }
View Code

 上面代码有一个缺陷,在获取变量时都是指定当前类,也就是[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 }
View Code

这样真的结束了吗?不是的。当你运行上面代码时有可能会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 }
View Code

在 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

原文地址:https://www.cnblogs.com/EchoHG/p/8641908.html