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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任权利。
原文地址:https://www.cnblogs.com/chenxianming/p/5627579.html