YYModel源码阅读(一)

429 阅读10分钟

YYModel是iOS开发中经常使用到的JSON模型转换库, 注重高性能.

YYModel的源码准备分两部分阅读, 第一部分是YYClassInfo主要是对runtime方法的一些封装, 第二部分是NSObject+YYModel是json转模型的主要实现.

类型编码 (Type Encoding)

当通过runtime获取类中的属性时, 可以通过property_copyAttributeList函数获取属性对应的类型编码, 通过类型编码就可以知道属性的数据类型和属性修饰符等.

关于类型编码中各个符号对应的类型, 参考官方文档:官方参考文献1 官方参考文献2

为了更好的理解类型编码, 做了一个小测试

@interface YYPerson : NSObject

// T:i N:"" V:_age
@property (nonatomic, assign) int age;
// T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"" R:"" C:"" N:"" V:_name
@property (nonatomic, copy, readonly) NSString<UITableViewDelegate, UITabBarDelegate> *name;
// T:@"NSString" &:"" N:"" V:_address
@property (nonatomic, retain) NSString *address;
// T:@"NSArray" W:"" N:"" V:_itemsArray
@property (nonatomic, weak) NSArray *itemsArray;
// T:@ &:"" N:"" V:_itItem
@property (nonatomic, strong) id itItem;
// T:@? C:"" N:"" V:_blockName
@property (nonatomic, copy) void(^blockName)(void);

@end
    
- (void)runtimeGetPropertyInfo {
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(self.class, &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        unsigned int attrCount = 0;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
        for (int j = 0; j < attrCount; j++) {
            objc_property_attribute_t attr = attrs[j];
            const char *attrName = attr.name;
            const char *attrValue = attr.value;
            NSString *attrStrValue = [NSString stringWithUTF8String:attrValue];
            NSLog(@"%s--%s:%@", name, attrName, attrStrValue.length > 0 ? attrStrValue : @"\"\"");
        }
    }
}

通过property_copyAttributeList获取和属性相关的类型编码, 在代码中已经将log信息做了注释, T对应的是属性的类型, 比如name属性是NSString类型, 同时还遵循了UITableViewDelegate UITabBarDelegate协议, 那么name的编码为"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"", 在YYModel中有对这部分做相应的处理.

来看一下YYModel中的自定义的类型编码类型

typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
    // 0000 0000 0000 0000 1111 1111
    // 属性类型
    YYEncodingTypeMask       = 0xFF, ///< mask of type value
    YYEncodingTypeUnknown    = 0, ///< unknown
    YYEncodingTypeVoid       = 1, ///< void
	...
    YYEncodingTypeCArray     = 22, ///< char[10] (for example)
    
    // 0000 0000 1111 1111 0000 0000
    YYEncodingTypeQualifierMask   = 0xFF00,   ///< mask of qualifier
	...
    YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway
    
    // 1111 1111 0000 0000 0000 0000
    // 属性修饰符相关
    YYEncodingTypePropertyMask         = 0xFF0000, ///< mask of property
	...
    YYEncodingTypePropertyDynamic      = 1 << 23, ///< @dynamic
};

由于代码较多, 只给出了部分代码, 完整代码还需查看YYModel源码.

使用NS_OPTIONS而非使用NS_ENUM定义枚举类型, NS_OPTIONS表示枚举选项可以多选, 而NS_ENUM表示枚举类型是互斥关系.

YYEncodingType中定义了三个互不关联的枚举类型, 通过YYEncodingTypeMask``YYEncodingTypeQualifierMask``YYEncodingTypePropertyMask三个掩码标识实现互不干扰, 通过&按位与的操作取出对应位的值(YYEncodingTypeMask & YYEncodingTypeBool)

三个掩码标识的二进制如下:

0000 0000 0000 0000 1111 1111

0000 0000 1111 1111 0000 0000

1111 1111 0000 0000 0000 0000

在YYModel中提供了一个函数, 将typeEncoding转换为YYModel自定义的枚举类型.

YYEncodingType YYEncodingGetType(const char *typeEncoding) {
	...
    switch (*type) {
        case '@': {
            // @?是block类型
            if (len == 2 && *(type + 1) == '?')
                return YYEncodingTypeBlock | qualifier;
            else
                return YYEncodingTypeObject | qualifier;
        }
        default: return YYEncodingTypeUnknown | qualifier;
    }
}

在通过type判断对象类型时, 对block类型做了特殊判断(@?是block类型).

YYClassIvarInfo成员变量信息

YYClassIvarInfo用于通过runtime获取和成员变量相关的信息.

YYClassIvarInfo是对struct objc_ivar相关信息的封装, 在runtime源码中查看Ivar的数据结构:

typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name; // 成员变量名称
    char * _Nullable ivar_type; // 成员变量的类型
    int ivar_offset; // 成员变量在实例中的偏移量
};

可以通过Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)获取cls相关的成员变量信息.

    unsigned int ivarCount = 0;
	//  获取和类相关联的成员变量
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivars[i];
        // 获取成员变量信息
        const char *name = ivar_getName(ivar);
        const char *typeEncoding = ivar_getTypeEncoding(ivar);
        ptrdiff_t offset = ivar_getOffset(ivar);
    }
    // 需要手动释放
    free(ivars);

YYClassIvarInfo中提供的属性接口:

@interface YYClassIvarInfo : NSObject

@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct, 成员变量
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name, 变量名称, _age格式
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset, 成员变量在实例变量内存中的偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding, 编码 见苹果官方文档
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type, 自定义枚举type

- (instancetype)initWithIvar:(Ivar)ivar;

@end

@implementation YYClassIvarInfo

- (instancetype)initWithIvar:(Ivar)ivar {
    if (!ivar) return nil;
    self = [super init];
    _ivar = ivar;
    // const char *修饰, name不可变
    const char *name = ivar_getName(ivar);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    _offset = ivar_getOffset(ivar);
    const char *typeEncoding = ivar_getTypeEncoding(ivar);
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        _type = YYEncodingGetType(typeEncoding);
    }
    return self;
}

@end

YYClassMethodInfo方法信息

YYClassMethodInfo用于通过runtime获取和方法相关的信息, 在runtime源码中查看objc_method的数据结构:

typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name; // 方法名称
    char * _Nullable method_types; // 方法类型
    IMP _Nonnull method_imp; // 方法的实现
};

YYClassMethodInfo提供了和方法相关的信息

@interface YYClassMethodInfo : NSObject

@property (nonatomic, assign, readonly) Method method;                  ///< method opaque struct, 方法
@property (nonatomic, strong, readonly) NSString *name;                 ///< method name, 方法名称
@property (nonatomic, assign, readonly) SEL sel;                        ///< method's selector, 方法选择器
@property (nonatomic, assign, readonly) IMP imp;                        ///< method's implementation, 方法实现
@property (nonatomic, strong, readonly) NSString *typeEncoding;         ///< method's parameter and return types, 方法编码格式 "@16@0:8" "v16@0:8"
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   ///< return value's type, 返回值类型 "@"
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type, 参数类型

- (instancetype)initWithMethod:(Method)method;

@end
    
@implementation YYClassMethodInfo

- (instancetype)initWithMethod:(Method)method {
    if (!method) return nil;
    self = [super init];
    _method = method;
    // -(void)runtimeGetIvarInfo;
    // 获取方法选择器
    _sel = method_getName(method);
    // 获取方法实现
    _imp = method_getImplementation(method);
    // 获取方法名称 runtimeGetIvarInfo
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    // 方法编码 "@16@0:8"
    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);
    }
    // 参数个数 2 第一个参数是self 类型是@ 第二个参数是方法本身sel 类型是:
    unsigned int argumentCount = method_getNumberOfArguments(method);
    if (argumentCount > 0) {
        NSMutableArray *argumentTypes = [NSMutableArray new];
        for (unsigned int i = 0; i < argumentCount; i++) {
            char *argumentType = method_copyArgumentType(method, i);
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;
    }
    return self;
}

@end

YYClassMethodInfo是对struct objc_method的封装提供了OC的访问方式, 注释中标明了以方法-(void)runtimeGetIvarInfo为例, 获取各种方法相关信息的值, 供参考.

YYClassPropertyInfo属性信息

YYClassPropertyInfo用于通过runtime获取和属性相关的信息, struct property_t的数据结构:

struct property_t {
    const char *name;
    const char *attributes;
};

objc_property_attribute_t数据结构:

typedef struct {
    const char * _Nonnull name;
    const char * _Nonnull value;
} objc_property_attribute_t;

YYClassPropertyInfo中提供的属性

@interface YYClassPropertyInfo : NSObject

@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct 属性结构体
@property (nonatomic, strong, readonly) NSString *name;           ///< property's name 属性名称
@property (nonatomic, assign, readonly) YYEncodingType type;      ///< property's type, 自定义枚举类型, 可以理解为属性的修饰符 nonatomic strong readonly等
@property (nonatomic, strong, readonly) NSString *typeEncoding;   ///< property's encoding value 类型编码
@property (nonatomic, strong, readonly) NSString *ivarName;       ///< property's ivar name 成员变量名称, 这里和属性名称还是有区别的, 只有类型编码为V的时候表示是实例变量, 成员变量才会被赋值
@property (nullable, nonatomic, assign, readonly) Class cls;      ///< may be nil
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil, 属性遵循的协议 可以理解为泛型
@property (nonatomic, assign, readonly) SEL getter;               ///< getter (nonnull) getter方法
@property (nonatomic, assign, readonly) SEL setter;               ///< setter (nonnull) setter方法

- (instancetype)initWithProperty:(objc_property_t)property;

@end
- (instancetype)initWithProperty:(objc_property_t)property {
	...
    for (unsigned int i = 0; i < attrCount; i++) {
        switch (attrs[i].name[0]) {
            // T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
            // T:"@\"NSString\""
            case 'T': { // Type encoding
                if (attrs[i].value) {
                    // T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
                    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
                    type = YYEncodingGetType(attrs[i].value);
                    
                    // 如果是实例对象类型
                    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
                        // @"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
                        
                        // 获取属性类型
                        NSString *clsName = nil;
                        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
                            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
                        }
                        
                        // 获取属性遵循的协议
                        NSMutableArray *protocols = nil;
                        while ([scanner scanString:@"<" intoString:NULL]) {
                            NSString* protocol = nil;
                            if ([scanner scanUpToString:@">" intoString: &protocol]) {
                                if (protocol.length) {
                                    if (!protocols) protocols = [NSMutableArray new];
                                    [protocols addObject:protocol];
                                }
                            }
                            [scanner scanString:@">" intoString:NULL];
                        }
                        _protocols = protocols;
                    }
                }
            } break;
            // 实例变量 _name
            case 'V': { // Instance variable
                if (attrs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                }
            } break;
            default: break;
        }
    }
    
    _type = type;
    if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }
    return self;
}

这里需要注意的是通过获取到的属性信息, 截取到属性的cls类型和遵循的协议protocols, 比如T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"", 代码中已经给出了注释.

YYClassInfo类相关信息

YYClassInfo封装了类相关的信息,在runtime源码中查看 struct objc_class的数据结构:

 struct objc_class {
 Class _Nonnull isa  OBJC_ISA_AVAILABILITY; // isa指针
 
 #if !__OBJC2__
 Class _Nullable super_class                              OBJC2_UNAVAILABLE; // 父类
 const char * _Nonnull name                               OBJC2_UNAVAILABLE; // 类名称
 long version                                             OBJC2_UNAVAILABLE; // 版本号
 long info                                                OBJC2_UNAVAILABLE; // 信息
 long instance_size                                       OBJC2_UNAVAILABLE; // 实例变量大小
 struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE; // 成员变量列表
 struct objc_method_list * _Nullable * _Nullable methodLists                OBJC2_UNAVAILABLE; // 方法列表
 struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE; // 缓存列表
 struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE; // 协议列表
 #endif
 
 } OBJC2_UNAVAILABLE;

cache主要存储的是方法缓存, 当一个实例变量需要调用方法时, 会先从缓存列表中查找方法, 提高方法的调用效率.

YYClassInfo提供的属性和方法信息

@interface YYClassInfo : NSObject

@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object, 父类
@property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< class's meta class object, 元类
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class 是否是元类
@property (nonatomic, strong, readonly) NSString *name; ///< class name, 类名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info, 父类信息
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars, 成员变量列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods, 方法列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties, 属性列表

- (void)setNeedUpdate;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;

@end

YYClassInfo中提供的构造方法

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    
    // 单例 classCache:当前类的缓存 metaCache: 元类缓存
    static CFMutableDictionaryRef classCache;
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    
    // 创建单例, 一个类的相关信息是相同的, 进行缓存, 避免每次都通过runtime转换class, 提高性能.
    // 信号量锁
    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);
    // 从缓存中取信息info
    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];
        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;
}

因为一个类的信息在被装载到内存以后, 类的信息是不经常发生变化的, 所以可以对类信息做相应的内存缓存, 提高查找效率.

代码中使用static修饰局部变量classCache和metaCache(关于static修饰变量可以查看汇编分析static), 在访问临界资源时使用信号量dispatch_semaphore_t加锁.

YYClassInfo中的更新操作

- (void)_update {
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;
    
    Class cls = self.cls;
    // 获取方法列表, 存储的是YYClassMethodInfo实例变量
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount);
    if (methods) {
        NSMutableDictionary *methodInfos = [NSMutableDictionary new];
        _methodInfos = methodInfos;
        for (unsigned int i = 0; i < methodCount; i++) {
            YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
            if (info.name) methodInfos[info.name] = info;
        }
        free(methods);
    }
    
    // 获取属性列表 存储的是YYClassPropertyInfo实例变量
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
    if (properties) {
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        free(properties);
    }
    
    // 获取成员变量列表 存储的是YYClassIvarInfo实例变量
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }
    
    if (!_ivarInfos) _ivarInfos = @{};
    if (!_methodInfos) _methodInfos = @{};
    if (!_propertyInfos) _propertyInfos = @{};
    
    _needUpdate = NO;
}

YYClassInfo中的更新其实就是重新获取了和类相关的成员变量列表 方法列表 属性列表, 并更新对应的ivarInfos methodInfos propertyInfos.

什么情况下需要更新呢? 比如当通过runtime动态向类中添加方法和属性等操作, 就需要更新类的信息.(成员变量在类信息中属于只读, 不能动态修改).

思考

  1. 类型编码 type-encoding
  2. runtime使用