iOS源码解析—YYModel(YYClassInfo)

概述

​ YYModel是封装的一个用来实现JSON格式数据和Model互转的强大库。相比较其他库来说,例如JSONModel、MJExtensions来说,性能更高一些,本文旨在分析YYModel的代码,通过实际案例来进一步说明。

文件结构

​ 相信使用过MJExtensions的iOS开发者来说,接触YYModel并不是很陌生,因为两者在某些思路上是相似的,例如都用了category的方式来实现,Protocol中的方法都有着相似的功能,例如黑白名单、Property名称和dictionary中key中的对应关系等。YYModel的文件包括:

  • YYModel.h
  • YYClassInfo.h、YYClassInfo.m
  • NSObject+YYModel.h、NSObject+YYModel.m

其中YYModel.h是公共头文件、YYClassInfo是针对NSObject的Ivar、Property、Method进行封装的类、NSObject+YYModel是具体负责转JSON和model的category。本篇分析一下YYClassInfo。

YYClassInfo

查看代码发现,NSObject对象中定义一个指针isa,如下:

@interface NSObject  {
    Class isa  OBJC_ISA_AVAILABILITY;
}

isa指向Class类型,Class类型是objc_class的别名,而objc_class是一个结构体,如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

主要信息包含:isa指向Class的meta类、super_class指向Class的父类。具体关系可以参考下图:

iOS源码解析—YYModel(YYClassInfo)_第1张图片
1-1.png

name是名称、objc_ivar_list是成员变量链表、methodLists是成员方法链表、protocols是附属协议链表。关于Class结构和runtime的文档,可以参考苹果官方文档和http://www.cocoachina.com/ios/20141031/10105.html。

YYClassInfo封装了Class对象的各个元素,代码如下:

@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; //关联的Class对象
@property (nullable, nonatomic, assign, readonly) Class superCls; //父类
@property (nullable, nonatomic, assign, readonly) Class metaCls;  //元类
@property (nonatomic, readonly) BOOL isMeta; //是否是元类
@property (nonatomic, strong, readonly) NSString *name; //Class名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; //父类Class对应的YYClassInfo
@property (nullable, nonatomic, strong, readonly) NSDictionary *ivarInfos; //ivar键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary *methodInfos; //方法键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary *propertyInfos;  //附属协议键值对
@end

YYClassInfo对象通过classInfoWithClass:方法根据Class创建一个YYClassInfo对象。代码注释如下:

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef classCache;
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{ //创建键值对
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls]; //创建YYClassInfo对象
        if (info) { //存入缓存
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

该方法用了一个键值对来存储创建好的YYClassInfo对象,key是Class对象,value是classInfo对象,当多次调用classInfoWithClass:方法时,如果Class对象相同,则直接从缓存中取出classInfo对象,不用每次都创建一个新的classInfo对象,提高了性能。同时检查_needUpdate变量,如有,刷新classInfo对象的内部属性值。

_needUpdate是YYClassInfo的一个成员变量,当Class的信息更新时,设置_needUpdate为YES,表示需要更新YYClassInfo。

- (void)setNeedUpdate {         //设置需要更新
    _needUpdate = YES;
}
- (BOOL)needUpdate {            //返回是否需要更新
    return _needUpdate;
}

如果_needUpdate设置为YES,则调用_update方法进行更新。第一次创建YYClassInfo对象时,会去调用一次_update方法,_update方法负责构建YYClassInfo中的ivarInfos、methodInfos和propertyInfos,这三个键值对分别维护了YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo信息,代码注释如下:

- (void)_update {
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;
    
    Class cls = self.cls;
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount); //获取Method数组
    if (methods) {
        NSMutableDictionary *methodInfos = [NSMutableDictionary new];
        _methodInfos = methodInfos;
        for (unsigned int i = 0; i < methodCount; i++) {
            YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; //用YYClassMethodInfo封装Method中的信息
            if (info.name) methodInfos[info.name] = info; //存入methodInfos
        }
        free(methods);
    }
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); //获取objc_property_t数组
    if (properties) {
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; //用YYClassPropertyInfo封装objc_property_t中的信息
            if (info.name) propertyInfos[info.name] = info; //存入propertyInfos
        }
        free(properties);
    }
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount); //获取Ivar数组
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; //用YYClassIvarInfo封装Ivar中的信息
            if (info.name) ivarInfos[info.name] = info; //存入ivarInfos
        }
        free(ivars);
    }
    if (!_ivarInfos) _ivarInfos = @{};
    if (!_methodInfos) _methodInfos = @{};
    if (!_propertyInfos) _propertyInfos = @{};
    _needUpdate = NO;
}

例如定义一个Student的类,如下:

@interface College : NSObject
@property (nonatomic, copy) NSString *name;
@end

@interface Student : NSObject 
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) College *college;
@end

Student *student = [[Student alloc] init];
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:[student class]];

通过YYClassInfo的初始化方法classInfoWithClass创建,得到对象如下:

iOS源码解析—YYModel(YYClassInfo)_第2张图片
1-2.png

下面分析一下ivarInfos、methodInfos和propertyInfos中存储的对象:

  1. YYClassIvarInfo

    通过runtime的class_copyIvarList方法得到Ivar数组, Ivar是iOS中描述成员变量的数据结构,如下:

      typedef struct objc_ivar *Ivar;
      struct objc_ivar {
          char *ivar_name OBJC2_UNAVAILABLE;     //成员变量名称
          char *ivar_type OBJC2_UNAVAILABLE; //成员变量类型
          int ivar_offset OBJC2_UNAVAILABLE; //成员变量偏移值
      #ifdef __LP64__
          int space OBJC2_UNAVAILABLE;
      #endif
      }  
    

    用YYClassIvarInfo来封装Ivar中的信息,如下:

      @interface YYClassIvarInfo : NSObject
      @property (nonatomic, assign, readonly) Ivar ivar;              //关联的Ivar变量
      @property (nonatomic, strong, readonly) NSString *name;         //成员变量名
      @property (nonatomic, assign, readonly) ptrdiff_t offset;       //成员变量偏移值
      @property (nonatomic, strong, readonly) NSString *typeEncoding; //成员变量类型编码
      @property (nonatomic, assign, readonly) YYEncodingType type;    //类型枚举
      @end
    

    initWithIvar:方法通过Ivar来构建YYClassIvarInfo对象,注释如下:

      - (instancetype)initWithIvar:(Ivar)ivar {
          if (!ivar) return nil;
          self = [super init];
          _ivar = ivar; //关联Ivar对象
          const char *name = ivar_getName(ivar); //获取名称
          if (name) {
              _name = [NSString stringWithUTF8String:name];
          }
          _offset = ivar_getOffset(ivar); //获取偏移值
          const char *typeEncoding = ivar_getTypeEncoding(ivar); //获取Ivar的类型编码
          if (typeEncoding) { //如果有类型编码
              _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
              _type = YYEncodingGetType(typeEncoding); //根据类型编码查找对应类型枚举
          }
          return self;
      }
    

    typeEncoding是iOS规定的一套类型编码,用不同的字符表示不同的类型,例如@表示对象类型,B表示bool类型,具体参考苹果官方文档。通过YYEncodingGetType方法可以通过类型编码映射到对应的枚举。例如student的成员变量name对应如下:

iOS源码解析—YYModel(YYClassInfo)_第3张图片
1-3.png
  1. YYClassMethodInfo

    通过class_copyMethodList方法得到Method数组,Method是iOS中描述成员方法数据结构objc_method,如下:

    struct objc_method {
        SEL method_name OBJC2_UNAVAILABLE;       //方法名
        char *method_types OBJC2_UNAVAILABLE;    //方法参数
        IMP method_imp OBJC2_UNAVAILABLE;        //方法的实现
    }  
    

    里面主要包含一个SEL和IMP,SEL是方法的标识符,IMP是真正实现方法的函数指针,OC在调用对象的方法时,转化如下:

    [student mark];
    ->转化为objc_msgSend(id, SEL, ...) //第一个参数是student对象,第二个参数是SEL类型的@selector(mark),后续参数是mark方法要传的参数。
    ->遍历Class对象的method数组,通过selector找到相应Method对应的IMP,调用IMP,并将参数传入。
    

    用YYClassMethodInfo来封装Method中的信息,如下:

    @interface YYClassMethodInfo : NSObject
    @property (nonatomic, assign, readonly) Method method; //关联的Method变量
    @property (nonatomic, strong, readonly) NSString *name; //成员方法名
    @property (nonatomic, assign, readonly) SEL sel; //SEL标识符
    @property (nonatomic, assign, readonly) IMP imp; //IMP指针
    @property (nonatomic, strong, readonly) NSString *typeEncoding;//类型编码
    @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; //方法返回类型编码
    @property (nullable, nonatomic, strong, readonly) NSArray *argumentTypeEncodings; //方法参数类型编码数组
    @end
    

    initWithMethod:方法通过Method构建YYClassMethodInfo,代码注释如下:

    - (instancetype)initWithMethod:(Method)method {
        if (!method) return nil;
        self = [super init];
        _method = method; //关联Method
        _sel = method_getName(method); //获取SEL
        _imp = method_getImplementation(method); //获取IMP
        const char *name = sel_getName(_sel); //获取方法名
        if (name) {
            _name = [NSString stringWithUTF8String:name];
        }
        const char *typeEncoding = method_getTypeEncoding(method); //获取类型编码
        if (typeEncoding) {
            _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        }
        char *returnType = method_copyReturnType(method); //方法返回类型
        if (returnType) {
            _returnTypeEncoding = [NSString stringWithUTF8String:returnType];
            free(returnType);
        }
        ...
        return self;
    }
    

    由于[a bb:cc]在运行时会被转化为objc_msgSend(a, @selector(bb),cc),第一个参数表示发送消息的目的对象,第二个参数表示消息的SEL标识,第三个参数为入参cc。所以Method的typeEncoding除了包含返回值类型和bb:方法的参数类型,还多包含前两个参数。

    例如Student的name的setter方法,如下:

iOS源码解析—YYModel(YYClassInfo)_第4张图片
1-4.png

typeEncoding共4个参数,第一个编码是"v"(返回值是void类型)、第二个编码是"@"(发送消息的目的对象是NSObject类型),第三个编码是":"(类型是SEL),第四个是编码是"@"(setName:的参数是NSString类型)。

  1. YYClassPropertyInfo

    通过class_copyPropertyList方法得到objc_property_t类型的数组,objc_property_t是描述Class对象property属性的结构,YYClassPropertyInfo封装objc_property_t相关信息。

    @interface YYClassPropertyInfo : NSObject
    @property (nonatomic, assign, readonly) objc_property_t property; //关联的objc_property_t
    @property (nonatomic, strong, readonly) NSString *name; //property名
    @property (nonatomic, assign, readonly) YYEncodingType type; //property的类型
    @property (nonatomic, strong, readonly) NSString *typeEncoding; //类型编码
    @property (nonatomic, strong, readonly) NSString *ivarName; //Ivar名
    @property (nullable, nonatomic, assign, readonly) Class cls; //
    @property (nullable, nonatomic, strong, readonly) NSArray *protocols; //
    @property (nonatomic, assign, readonly) SEL getter; //getter方法对应的SEL
    @property (nonatomic, assign, readonly) SEL setter; //setter方法对应的SEL
    @end
    

    initWithProperty:方法通过objc_property_t创建YYClassPropertyInfo对象,代码注释如下:

    - (instancetype)initWithProperty:(objc_property_t)property {
         ...
         _property = property; //关联objc_property_t
        const char *name = property_getName(property); //获得property名称
        if (name) {
            _name = [NSString stringWithUTF8String:name];
        }
         YYEncodingType type = 0;
        unsigned int attrCount;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); //获取property的参数数组,
      //遍历attrs数组,获取相关信息,例如
        for (unsigned int i = 0; i < attrCount; i++) {
             switch (attrs[i].name[0]) { 
                 case 'T': {...} break; //T对应typeEncoding
                 case 'V': { //V对应ivar值
                    if (attrs[i].value) {
                        _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                    }
                } break;
                 ... //"R、C、&、N"等property的修饰器类型
            }
     }
    }
    

    通过property_copyAttributeList方法获取attrs数组,每个元素是objc_property_attribute_t类型的结构,如下:

    typedef struct {
        const char *name;    //标识
        const char *value;   //值
    } objc_property_attribute_t;
    

    name的标识不同,例如T、V等,对应的value有不同含义。例如Student的name属性,如下:

iOS源码解析—YYModel(YYClassInfo)_第5张图片
1-5.png

_typeEncoding的前缀是@,因为name是NSString类型,注意这里的_name表示property的名称,而_ivarName表示property对应的成员变量的名称,_getter和_setter分别是property生成的方法。

总结

学习YYClassInfo对于了解iOS的Class结构和runtime有一定帮助。对于我来说,发现在写的过程中,自己还有很多理解不到位的地方。所以写文章能够促进自己更加仔细的阅读源代码,文中如有不足之处,希望各位大神指正。

参考资料

YYModel源代码分析

Objective-C Runtime 运行时之二:成员变量与属性

苹果官方文档 Type Encodings

你可能感兴趣的:(iOS源码解析—YYModel(YYClassInfo))