• 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

  • 相关阅读:
    安卓反编译
    Icesugar Gourd
    php文件写入
    java 序列化与反序列化
    Toj Dominoes Game
    adb&frida
    Markdown 测试用例
    iview InputNumber类输入框表单验证失效
    01背包问题(回溯算法实现)
    阅读作业二读Lost in CatB有感 by 李栋
  • 原文地址:https://www.cnblogs.com/EchoHG/p/8641908.html
Copyright © 2020-2023  润新知