zoukankan      html  css  js  c++  java
  • iOS runtime (二)(runtime学习之AutoCoding源码分析)

      在上一篇文章中,学习了runtime中的各个重要的知识点,接下来就是要开始运用了。主要是分析一些优秀开源库是如何运用runtime,提高工作效率的。


       AutoCoding

      AutoCoding 是一个NSObject的类别,它提供归档解档(即自动序列化和反序列化)对象。在介绍这个开源库之前,先简单过一下iOS中对象归档解档,这个并不是重点,只是为了抛出问题,所以不会详讲。在iOS中对象序归档解档会使用到NSKeyedArchiver和NSKeyedUnarchiver,接下来是示例代码:

    创建一个需要归档解档的对象的类,它需要遵循NSSecureCoding协议并实现相应接口,决定它是如何归档解档对象的成员变量。

    //MyDataInfo.h
    #import <Foundation/Foundation.h>
    
    @interface MyDataInfo : NSObject <NSSecureCoding>
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @end
    //MyDataInfo.m
    #import "MyDataInfo.h"
    NSString *const kNameKey = @"name";
    NSString *const kAgeKey = @"age";
    @implementation MyDataInfo
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        [aCoder encodeObject:self.name forKey:kNameKey];
        [aCoder encodeInteger:self.age forKey:kAgeKey];
    }
    
    - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        self.name = [aDecoder decodeObjectForKey:kNameKey];
        self.age = [aDecoder decodeIntegerForKey:kAgeKey];
        return self;
    }
    
    + (BOOL)supportsSecureCoding
    {
        return YES;
    }
    @end

    接下来就是对这个类进行归档解档的过程

    //ViewController.m
    .......
    - (void)viewDidLoad {
        [super viewDidLoad];
        MyDataInfo *dataInfo = [[MyDataInfo alloc] init];
        dataInfo.name = @"Davi";
        dataInfo.age = 100;
        
        NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:dataInfo];
        
        MyDataInfo *unArchiveDataInfo = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
        
        NSLog(@"unArchiveDataInfo name:%@, age:%ld", unArchiveDataInfo.name, (long)unArchiveDataInfo.age);
    }
    .......

    运行代码,输出结果为,结果是毫无疑问的

    2016-06-30 20:16:10.482 TestAutoCoding[59320:54257381] unArchiveDataInfo name:Davi, age:100

    那么要抛出的问题什么呢?在日常开发当中,我们要保存的对象,它们可能会有组合的关系,也有继承的关系,也有可能有N多属性,并且也会属于很多不同的类,那么此时如果要归档解档这些对象,都需要像MyDataInfo一样遵循NSSecureCoding协议并实现相应接initWithCoder:,encodeWithCoder:,为每个自属性定义一个key,类似于NSString *const kNameKey = @"name",然后在encodeWithCoder:接口内写encode代码,在initWithCoder:接口内写

    decode代码。当需要归档解档的类有很多很多,这部分就是一个重复的苦力活,而且会容易出错。AutoCoding就是为解决这个问题出现的。github上对它的描述是这样的

    AutoCoding is a category on NSObject that provides automatic support for NSCoding to any object. This means that rather than having to implement the initWithCoder: and encodeWithCoder: methods yourself, all the model classes in your app can be saved or loaded from a file without you needing to write any additional code.

     意思就是你不需要为要归档解档的model类写任何代码,就能实现上面MyDataInfo说的归档存档。那么它是怎么做到的?

    首先,在NSObject (AutoCoding) 类别中实现以下两个接口:

    - (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        [self setWithCoder:aDecoder];    //后续讲解
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        for (NSString *key in [self codableProperties])  //后续讲解
        {
            id object = [self valueForKey:key];
            if (object) [aCoder encodeObject:object forKey:key];
        }
    }

    这样完成了第一步,要归档解档的model类只要没有实现这两个方法,都会被调用到NSObject 类别以上这两个方法。接下先说归档,在encodeWithCoder:接口内可以看到[self codableProperties],它的作用是拿到对象(它所属类的继承体系中除NSObject外)的所有属性名及类型。

    - (NSDictionary *)codableProperties
    {
        //添加一个字典关联到这个类上,目的是做缓存,只要做一次获取成员变量名字,和类型。其中名字为key,类型为对应值value
        __autoreleasing NSDictionary *codableProperties = objc_getAssociatedObject([self class], _cmd);
        if (!codableProperties)
        {
            codableProperties = [NSMutableDictionary dictionary];
            Class subclass = [self class];
            //从当前类开始向父类遍历,一直到NSObject类就停止
            while (subclass != [NSObject class])
            {
                //获取当前类所有的变量名-类型,添加到字典codableProperties里
                [(NSMutableDictionary *)codableProperties addEntriesFromDictionary:[subclass codableProperties]/*紧跟着讲*/];
                subclass = [subclass superclass];
            }
            codableProperties = [NSDictionary dictionaryWithDictionary:codableProperties];
            
            objc_setAssociatedObject([self class], _cmd, codableProperties, OBJC_ASSOCIATION_RETAIN);
        }
        return codableProperties;
    }

    接下来就是[subclass codableProperties],它的作用获取类中所有的变量名、类型,以字典作为返回值

    + (NSDictionary *)codableProperties
    {
        //deprecated
        SEL deprecatedSelector = NSSelectorFromString(@"codableKeys");
        if ([self respondsToSelector:deprecatedSelector] || [self instancesRespondToSelector:deprecatedSelector])
        {
            NSLog(@"AutoCoding Warning: codableKeys method is no longer supported. Use codableProperties instead.");
        }
        deprecatedSelector = NSSelectorFromString(@"uncodableKeys");
        if ([self respondsToSelector:deprecatedSelector] || [self instancesRespondToSelector:deprecatedSelector])
        {
            NSLog(@"AutoCoding Warning: uncodableKeys method is no longer supported. Use ivars, or synthesize your properties using non-KVC-compliant names to avoid coding them instead.");
        }
        deprecatedSelector = NSSelectorFromString(@"uncodableProperties");
        NSArray *uncodableProperties = nil;
        if ([self respondsToSelector:deprecatedSelector] || [self instancesRespondToSelector:deprecatedSelector])
        {
            uncodableProperties = [self valueForKey:@"uncodableProperties"];
            NSLog(@"AutoCoding Warning: uncodableProperties method is no longer supported. Use ivars, or synthesize your properties using non-KVC-compliant names to avoid coding them instead.");
        }
        
        unsigned int propertyCount;
        __autoreleasing NSMutableDictionary *codableProperties = [NSMutableDictionary dictionary];
        objc_property_t *properties = class_copyPropertyList(self, &propertyCount);
        for (unsigned int i = 0; i < propertyCount; i++)
        {
            //获取属性名
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            __autoreleasing NSString *key = @(propertyName);
    
            //检查是否可coding
            if (![uncodableProperties containsObject:key])
            {
                //拿到property的Type Encodings,这部分不了解可去看官方文档Type Encodings了解下规则及runtime中对应的相关接口,这里都用到,也可以看我上一篇文章
                Class propertyClass = nil;
                char *typeEncoding = property_copyAttributeValue(property, "T");
                switch (typeEncoding[0])
                {
              //表示是个对象
    case '@': {
                //根据Type Encodings规则得出来的算法而已,目的截取出属性类型
    if (strlen(typeEncoding) >= 3) { char *className = strndup(typeEncoding + 2, strlen(typeEncoding) - 3); __autoreleasing NSString *name = @(className); NSRange range = [name rangeOfString:@"<"]; if (range.location != NSNotFound) { name = [name substringToIndex:range.location]; } propertyClass = NSClassFromString(name) ?: [NSObject class]; free(className); } break; } case 'c': case 'i': case 's': case 'l': case 'q': case 'C': case 'I': case 'S': case 'L': case 'Q': case 'f': case 'd': case 'B': {
                //都当成是NSNumber型 propertyClass
    = [NSNumber class]; break; } case '{': { propertyClass = [NSValue class]; break; } } free(typeEncoding); if (propertyClass) { //获取变量名 char *ivar = property_copyAttributeValue(property, "V"); if (ivar) { //检查属性是否有编译器帮忙生成的成员变量名带_线的 __autoreleasing NSString *ivarName = @(ivar); if ([ivarName isEqualToString:key] || [ivarName isEqualToString:[@"_" stringByAppendingString:key]]) { codableProperties[key] = propertyClass; } free(ivar); } else { //检查属性是否是 dynamic 和 readwrite的 char *dynamic = property_copyAttributeValue(property, "D"); char *readonly = property_copyAttributeValue(property, "R"); if (dynamic && !readonly) { codableProperties[key] = propertyClass; } free(dynamic); free(readonly); } } } } free(properties); return codableProperties; }

    再看回encodeWithCoder:(NSCoder *)aCoder

    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        for (NSString *key in [self codableProperties])  
        {
            id object = [self valueForKey:key];
            if (object) [aCoder encodeObject:object forKey:key];
        }
    }

    不难知道,其实就是遍历所有属性名字,并以属性名字作为key,利用KVC先把属性值拿到,再属性名字作为key调用encodeObject。原理就是这样,解档也差不多就不再写了,关键是拿到对象的所有属性名称及类型,利用KVC获取和设置属性值,再以属性名称作为Key避免手动定义。

    使用AutoCoding之后,MyDataInfo直接变成下面这样就可以了,结果与手动写是一样的,大大减小工作量,提高开发效率,又减少出错率。

    //MyDataInfo.h
    #import <Foundation/Foundation.h>
    @interface MyDataInfo : NSObject 
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @end
    
    //MyDataInfo.m
    #import "MyDataInfo.h"
    @implementation MyDataInfo
    @end

    这里运用到runtime相关知识主要有我上篇文章介绍过的成员变量与属性、Type Encodings。


    作者:xianmingchen
    出处:http://www.cnblogs.com/chenxianming/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任权利。
  • 相关阅读:
    微软面试100 题题解
    二元查找树转变成排序的双向链表(树)
    筆試
    PE注入
    内核网络通信
    哈哈哈
    OpenCV 学习
    第一次研究VM程序中的爆破点笔记
    SE2.3.4各试用限制调试笔记
    破解相关书籍
  • 原文地址:https://www.cnblogs.com/chenxianming/p/5627579.html
Copyright © 2011-2022 走看看