原 荐 阅读YYModel

栏目: IOS · 发布时间: 7年前

内容简介:YYModel库中涉及到Runtime、CF API、信号和锁、位的操作。学习该库可以学习到使用Runtime获取类的信息,包括:类属性信息、类ivar信息、类方法、类型编码;使用runtime底层技术进行方法调用,也就是objc_msgSend方法的使用;dispatch_semaphore_t信号锁的使用;CF框架中CFMutableDictionaryRef/CFMutableDictionaryRef对象的操作;位的操作。首先会介绍下YYModel的使用,作为下面类属性的定义和json数据中的ke

YYModel库中涉及到Runtime、CF API、信号和锁、位的操作。学习该库可以学习到使用Runtime获取类的信息,包括:类属性信息、类ivar信息、类方法、类型编码;使用runtime底层技术进行方法调用,也就是objc_msgSend方法的使用;dispatch_semaphore_t信号锁的使用;CF框架中CFMutableDictionaryRef/CFMutableDictionaryRef对象的操作;位的操作。

  • 简单使用介绍
    • 简单的转化
    • 自定义属性和json字段的映射配置
    • 黑名单和白名单配置
    • 类型映射配置
  • 预备知识
    • Type Encodings
    • Property Type String
  • 代码解析
    • 类信息的获取
    • 配置信息的获取
    • 模型对象的转换
  • 其它重要知识点
    • 位操作
    • 信号和锁
    • CF框架API

YYModel使用介绍

首先会介绍下YYModel的使用,作为下面 代码解析 章节的铺垫, 代码解析 章节以使用方式作为入口点切入,研究框架整体的实现思路、步骤以及每个步骤使用的详细技术。

简单的转化

类属性的定义和json数据中的key是一致的,这种情况最为简单,不用配置映射关系,使用 NSObject+YYModel 的方法 yy_modelWithDictionary 获取 yy_modelWithJSON 即可以把数据反序列化为对象。

比如元素的JSON数据如下所示:

{
      "id": 7975492,
      "title": "同学们,开学了,准备好早起了吗?\n",
      "source_text": "来自新浪微博",
      "share_count": 1,
      "praise_num": 999,
      "comment_num": 0,
      "is_praise": 0
    }

定义的数据模型类如下:

@interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel>

@property (nonatomic, copy) NSString *source_text;
@property (nonatomic, assign) NSInteger share_count;
@property (nonatomic, assign) NSInteger praise_num; ///< 点赞数
@property (nonatomic, assign) BOOL is_praise; ///< 是否已点赞
@property (nonatomic, assign) NSInteger comment_num; ///< 评论数量

@end

转换后的结果如下:

原 荐 阅读YYModel

自定义属性和json字段的映射配置

多数情况下,服务端的接口是为了多平台开发的,不同平台的属性定义标准、格式不一样,多数情况下我们需要把服务端的数据个数映射为平台适应的格式,这种情况需要用到配置自定义属性和json字段的映射,可以重写 YYModel 协议的方法 modelCustomPropertyMapper 返回一个配置。

定义的数据模型类如下:

@interface IMYOriginalRecommendWeiboModel : IMYRecommendBaseModel <YYModel>

@property (nonatomic, copy) NSString *sourceText;
@property (nonatomic, assign) NSInteger shareCount;
@property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数
@property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞
@property (nonatomic, assign) NSInteger commentCount; ///< 评论数量

@end

映射关系配置如下:

@implementation IMYRecommendWeiboModel

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"sourceText"  : @"source_text",
             @"shareCount"  : @"share_count",
             @"praiseCount"  : @"praise_num",
             @"isPraise"  : @"is_praise",
             @"commentCount"  : @"comment_num",
             };
}

@end

转换后的结果如下:

原 荐 阅读YYModel

黑名单和白名单配置

黑名单配置如下:

@implementation IMYRecommendBlackListWeiboModel

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"sourceText"  : @"source_text",
             @"shareCount"  : @"share_count",
             @"praiseCount"  : @"praise_num",
             @"isPraise"  : @"is_praise",
             @"commentCount"  : @"comment_num",
             };
}

+ (NSArray<NSString *> *)modelPropertyBlacklist {
    return @[@"sourceText", @"shareCount"];
}

@end

反序列化的结果,配置在黑名单中的属性( sourceTextshareCount )不会被赋值

原 荐 阅读YYModel

白名单配置如下:

@implementation IMYRecommendWhiteListWeiboModel

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"sourceText"  : @"source_text",
             @"shareCount"  : @"share_count",
             @"praiseCount"  : @"praise_num",
             @"isPraise"  : @"is_praise",
             @"commentCount"  : @"comment_num",
             };
}

+ (NSArray<NSString *> *)modelPropertyWhitelist {
    return @[@"sourceText", @"shareCount"];
}
@end

反序列化的结果,只有配置在白名单中的属性( sourceTextshareCount )才会被赋值

原 荐 阅读YYModel

类型映射配置

类型映射配置用于属性是数组类型,需要配置该属性中元素的类类型,比如我们定义的数据模型如下,有个 users 指定是数组类型

@interface IMYRecommendExtendWeiboModel : IMYRecommendBaseModel <YYModel>

@property (nonatomic, copy) NSString *sourceText;
@property (nonatomic, assign) NSInteger shareCount;
@property (nonatomic, assign) NSInteger praiseCount; ///< 点赞数
@property (nonatomic, assign) BOOL isPraise; ///< 是否已点赞
@property (nonatomic, assign) NSInteger commentCount; ///< 评论数量
@property (nonatomic, strong) NSArray<IMYUser *> *users;

@end

类型映射配置如下:

@implementation IMYRecommendBlackListWeiboModel

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"sourceText" : @"source_text",
             @"shareCount" : @"share_count",
             @"praiseCount" : @"praise_num",
             @"isPraise" : @"is_praise",
             @"commentCount" : @"comment_num",
             };
}

+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"users" : [IMYUser class]};
}

@end

反序列化的结果如下:

原 荐 阅读YYModel

预备知识

首先需要了解的是苹果对于类型说明的的一些规范,包括了 Type EncodingsProperty Type String ,YYModel代码中重要的一部分就是对这些信息进行建模处理。

Type Encodings

Type Encodings 指的是属性、参数、变量的类型,比如基本数据类型有char、int、short、long、等;此外还有对象类型、类类型、数组类型、结构体类型、共用体类型、OC中的SEL类型等。其中Block 类型的比较特殊,该类型的的类型定义为:"@?",在YYModel中处理Block类型的代码如下:

case '@': {
    if (len == 2 && *(type + 1) == '?')
        return YYEncodingTypeBlock | qualifier;
    else
        return YYEncodingTypeObject | qualifier;
}

Property Type String

Property Type String 指的是property的内存属性、读写属性、原子属性、getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding,其中property的内存属性、读写属性、原子属性是只有属性名称,没有属性值,而property的getter/getter属性、属性对应的ivar名称以及属性本身的Type Encoding除了属性名称之外还会附加一个属性的值。查看property的属性名字和值可以使用 property_getAttributes 方法获取,返回的是一个 const char * 类型,如果需要对属性的名称和值进行详细的分析可以使用 property_copyAttributeList 获取到一个数组,数组的元素是 objc_property_attribute_t 这种结构体类型的,已经解析好了 namevalue ,方便使用。

假设定义有如下的属性 @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; ,使用 property_getAttributes 方法获取到的属性对应的属性描述如下: Ti,R,GisIntReadOnlyGetterintReadonlyGetter 属性对应的 objc_property_attribute_t 类型为 p ,基本的规则解释如下:

Type Encodings

以下是一段使用 property_getAttributes 方法获取属性名字和值得示例代码

定义一个测试的类包含有如下几个属性

@interface MyObj : NSObject
@property (nonatomic, copy) MyBlock block;
@property struct YorkshireTeaStruct structDefault;
@property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic;
@property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter;
@end

使用如下的代码解析property的属性

char *propertyAttributes = (char *)property_getAttributes(property);
    NSString *propertyAttributesString = [NSString stringWithUTF8String:propertyAttributes];
    NSLog(@"propertyAttributesString = %@", propertyAttributesString);

使用以上的代码对 MyObj 类的属性进行分析,打印的内容如下,可以对照 Property Type StringType Encodings 查看详细的说明:

propertyAttributesString = T@?,C,N,V_block
propertyAttributesString = T{YorkshireTeaStruct=ic},V_structDefault
propertyAttributesString = T@,R,&,N,V_idReadonlyRetainNonatomic
propertyAttributesString = Ti,R,GisIntReadOnlyGetter,V_intReadonlyGetter

代码解析

类信息的获取

类信息的获取主要是在 YYClassInfo 类的方法 _update 中处理的,包括类的类对象、父类的类对象、父类的类信息、是否是元类、属性信息、方法信息、ivar信息。实际上

方法信息

ivar信息 这两个信息并没有真正的使用到,但是框架中有进行处理,为了完整性还是稍作叙述。主要使用到的还是 propertyInfos

属性中的内容,后面的配置信息获取和模型对象转换都需要使用到。

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
// ...

流程时序图如下所示:

原 荐 阅读YYModel

获取类信息的操作主要都在 _update 方法中,主要步骤如下

  • class_copyMethodList 方法获取类中的方法信息,方法信息转换为YYClassMethodInfo对象,保存在methodInfos属性中
  • class_copyPropertyList 方法获取类中的属性信息,属性信息转换为YYClassPropertyInfo对象,保存在propertyInfos属性中
  • class_copyIvarList 方法获取类中的ivar信息,ivar信息转换为YYClassIvarInfo对象,保存在ivarInfos属性中

这里需要注意的是 class_copyMethodListclass_copyPropertyListclass_copyIvarList 涉及到内存的拷贝问题,使用完成之后需要使用C的方法free释放拷贝的内容,防止内存泄漏。上面有提到说

方法信息

ivar信息

这两个信息并没有真正的使用到,接下来会着重的介绍属性信息的获取中的一些细节。

- (void)_update {
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;
    
    Class cls = self.cls;
    // 获取类中的方法信息
    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);
    }
    
    // 获取类中的属性信息
    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);
    }
    
    // 获取类中的ivar信息
    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;
}

属性信息获取

属性信息主要包含属性名字、属性的Encoding 类型、属性所属的类、属性的getter/setter方法的选择子SEL。这些信息会保存在属性信息类 YYClassPropertyInfo 中,属性信息类 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
@property (nonatomic, strong, readonly) NSString *typeEncoding;   ///< property's encoding value
@property (nonatomic, strong, readonly) NSString *ivarName;       ///< property's ivar name
@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)
@property (nonatomic, assign, readonly) SEL setter;               ///< setter (nonnull)
// ...

属性处理主要在 initWithProperty 方法中进行,这部分的内容在前面的预备知识中的 Type EncodingsProperty Type String 有讲到了大部分,主要步骤如下:

  • 获取property名字
  • 读取property的属性名字和属性值,建立属性的Encoding模型
  • 处理getter/setter
- (instancetype)initWithProperty:(objc_property_t)property {
    if (!property) return nil;
    self = [super init];
    _property = property;
    // 获取property名字
    const char *name = property_getName(property);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    
    // 读取property的属性名字和属性值,建立属性的Encoding模型
    YYEncodingType type = 0;
    unsigned int attrCount;
    objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
    for (unsigned int i = 0; i < attrCount; i++) {
        switch (attrs[i].name[0]) {
            case 'T': { // Type encoding
                if (attrs[i].value) {
                    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
                    type = YYEncodingGetType(attrs[i].value);
                    
                    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
                        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;
            case 'V': { // Instance variable
                if (attrs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                }
            } break;
            case 'R': {
                type |= YYEncodingTypePropertyReadonly;
            } break;
            case 'C': {
                type |= YYEncodingTypePropertyCopy;
            } break;
            case '&': {
                type |= YYEncodingTypePropertyRetain;
            } break;
            case 'N': {
                type |= YYEncodingTypePropertyNonatomic;
            } break;
            case 'D': {
                type |= YYEncodingTypePropertyDynamic;
            } break;
            case 'W': {
                type |= YYEncodingTypePropertyWeak;
            } break;
            case 'G': {
                type |= YYEncodingTypePropertyCustomGetter;
                if (attrs[i].value) {
                    _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } break;
            case 'S': {
                type |= YYEncodingTypePropertyCustomSetter;
                if (attrs[i].value) {
                    _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } // break; commented for code coverage in next line
            default: break;
        }
    }
    if (attrs) {
        free(attrs);
        attrs = NULL;
    }
    
    _type = type;
    // 处理getter、setter方法
    if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }
    return self;
}

配置信息的获取

配置信息的获取主要流程时序图如下:

原 荐 阅读YYModel

主要有以下几个步骤:

  • 黑名单配置获取
  • 白名单配置获取
  • 容器类型中元素类型配置获取
  • 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据
  • 处理自定义的属性对于json数据key的映射配置

对应的代码如下,关键的地方有添加了注释:

- (instancetype)initWithClass:(Class)cls {
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];
    
    // Get black list
    // 黑名单配置获取
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    
    // Get white list
    // 白名单配置获取
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    
    // Get container property's generic class
    // 容器类型中元素类型配置获取
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                if (![key isKindOfClass:[NSString class]]) return;
                Class meta = object_getClass(obj);
                if (!meta) return;
                if (class_isMetaClass(meta)) {
                    tmp[key] = obj;
                } else if ([obj isKindOfClass:[NSString class]]) {
                    Class cls = NSClassFromString(obj);
                    if (cls) {
                        tmp[key] = cls;
                    }
                }
            }];
            genericMapper = tmp;
        }
    }
    
    // 递归遍历当前类和父类的propertyInfos属性,获取所有property的元数据
    // property的元数据对应的私有类为_YYModelPropertyMeta,其实就是YYClassPropertyInfo类对象的封装
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
        for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
            if (!propertyInfo.name) continue;
            if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
            if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
            _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                    propertyInfo:propertyInfo
                                                                         generic:genericMapper[propertyInfo.name]];
            if (!meta || !meta->_name) continue;
            if (!meta->_getter || !meta->_setter) continue;
            if (allPropertyMetas[meta->_name]) continue;
            allPropertyMetas[meta->_name] = meta;
        }
        curClassInfo = curClassInfo.superClassInfo;
    }
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
    
    // create mapper
    // 处理自定义的属性对于json数据key的映射配置
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            [allPropertyMetas removeObjectForKey:propertyName];
            
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                for (NSString *onePath in keyPath) {
                    if (onePath.length == 0) {
                        NSMutableArray *tmp = keyPath.mutableCopy;
                        [tmp removeObject:@""];
                        keyPath = tmp;
                        break;
                    }
                }
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                // 处理有多个属性绑定到同一个json数据key的配置
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in ((NSArray *)mappedToKey)) {
                    if (![oneKey isKindOfClass:[NSString class]]) continue;
                    if (oneKey.length == 0) continue;
                    
                    NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
                    if (keyPath.count > 1) {
                        [mappedToKeyArray addObject:keyPath];
                    } else {
                        [mappedToKeyArray addObject:oneKey];
                    }
                    
                    if (!propertyMeta->_mappedToKey) {
                        propertyMeta->_mappedToKey = oneKey;
                        propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
                    }
                }
                if (!propertyMeta->_mappedToKey) return;
                
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                // 处理有多个属性绑定到同一个json数据key的配置
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    // 处理默认的属性的映射配置,属性在json数据中的key就是属性的原始名称
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    // 最终把json数据key和属性的映射关系保存起来
    if (mapper.count) _mapper = mapper;
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
    if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;
    
    _classInfo = classInfo;
    _keyMappedCount = _allPropertyMetas.count;
    _nsType = YYClassGetNSType(cls);
    _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
    _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
    _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
    _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
    
    return self;
}

模型对象的转换

模型对象的转换的步骤时序图如下:

原 荐 阅读YYModel

入口函数 yy_modelSetWithDictionary 的功能如下:

  • 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据
    • 1.1、遍历json数据的key
    • 1.2、根据key从 meta->_mapper 配置中寻找映射的 _YYModelPropertyMeta 对象
    • 1.3、从json数据中读取值,给属性设置值。
  • 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性
    modelMeta->_allPropertyMetas
    

对应的代码如下,关键的地方有添加了注释:

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    

    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    // 这里做这个处理主要是为了优化性能,哪个少优先处理哪个,提高效率
    // 1、属性的映射配置的个数大于json数据元素的个数(if分支),优先处理json数据
    // 1.1、遍历json数据的key
    // 1.2、根据key从`meta->_mapper`配置中寻找映射的`_YYModelPropertyMeta`对象
    // 1.3、从json数据中读取值,给属性设置值。
    // * 因为`meta->_mapper`保存的是一对一的映射关系,所以另外需要额外的处理keypath映射和数组类型的key的映射
    // 2、json数据元素的个数大于属性的映射配置的个数(else分支),优先处理属性属性
    // 2.1、从`modelMeta->_allPropertyMetas`读取属性配置
    // 2.2、从json数据中读取值,给属性设置值
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        // 处理多个属性映射到同一个json数据的key
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            // 处理keypath,属性对应json数据key是多层类型的映射
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            // 处理数组类型映射,属性对应json数据key是多个的映射
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        // 处理属性多json数据的key的一对一映射,这种情况多个属性映射到同一个json数据的key处理方式和ModelSetWithDictionaryFunction方法的不一样,不过最终的结果是一样的
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

以上代码中使用到的两个方法 ModelSetWithDictionaryFunctionModelSetWithPropertyMetaArrayFunction ,这两个方法很简单,使用获取值方法 YYValueForMultiKeysYYValueForKeyPath ,根据 _YYModelPropertyMeta 对象的配置从json数据中获取值,然后调用 ModelSetValueForProperty 给对象的属性设置值。

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    __unsafe_unretained id model = (__bridge id)(context->model);
    while (propertyMeta) {
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    if (!propertyMeta->_setter) return;
    id value = nil;
    
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

获取值方法 YYValueForMultiKeysYYValueForKeyPath 也比较简单

YYValueForMultiKeys
YYValueForKeyPath
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
    id value = nil;
    for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
        value = dic[keyPaths[i]];
        if (i + 1 < max) {
            if ([value isKindOfClass:[NSDictionary class]]) {
                dic = value;
            } else {
                return nil;
            }
        }
    }
    return value;
}

static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
    id value = nil;
    for (NSString *key in multiKeys) {
        if ([key isKindOfClass:[NSString class]]) {
            value = dic[key];
            if (value) break;
        } else {
            value = YYValueForKeyPath(dic, (NSArray *)key);
            if (value) break;
        }
    }
    return value;
}

到了最关键的设置属性值的方法 ModelSetValueForProperty ,使用底层的 C语言 方法 objc_msgSend 给属性赋值,该方法区分类型处理值

  • 数值类型
  • NSFoundation类型,包含了NSArray、NSString、NSDictionary、NSData等类型以及对应的可变类型(如果存在)
  • 其余特殊类型,包含了SEL、block、Struct、Union、C指针、字符串指针等

需要注意:

  • 1、可变类型需要特殊处理,需要使用mutableCopy复制一个可变对象赋值给属性,否则使用可变类型的特有api比如addObject就会出现崩溃;
  • 2、json数据取出的值和property定义的值类型可能不一样,如果是数值类型,需要把数值转换为对应类型,否则编译不过;如果是NSFoundation类型,需要把Json数据转换为property定义的类型,否则类型会有问题,方法 objc_msgSend 可以设置成功,比如property中定义的是属性status_desc是NSString类型,方法 objc_msgSend 方法的参数传递的是一个NSDate类型,使用status_desc的方法比如length就会崩溃,因为类型变为NSDate了,在NSDate中找不到length方法

方法的部分如下:

static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) {
    if (meta->_isCNumber) {
        NSNumber *num = YYNSNumberCreateFromID(value);
        ModelSetNumberToProperty(model, num, meta);
        if (num) [num class]; // hold the number
    } else if (meta->_nsType) {
        if (value == (id)kCFNull) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
        } else {
            switch (meta->_nsType) {
                case YYEncodingTypeNSString:
                case YYEncodingTypeNSMutableString: {
                    if ([value isKindOfClass:[NSString class]]) {
                        if (meta->_nsType == YYEncodingTypeNSString) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
                        }
                    } else if ([value isKindOfClass:[NSNumber class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == YYEncodingTypeNSString) ?
                                                                       ((NSNumber *)value).stringValue :
                                                                       ((NSNumber *)value).stringValue.mutableCopy);
                    } else if ([value isKindOfClass:[NSData class]]) {
                        NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
                    } else if ([value isKindOfClass:[NSURL class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == YYEncodingTypeNSString) ?
                                                                       ((NSURL *)value).absoluteString :
                                                                       ((NSURL *)value).absoluteString.mutableCopy);
                    } else if ([value isKindOfClass:[NSAttributedString class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == YYEncodingTypeNSString) ?
                                                                       ((NSAttributedString *)value).string :
                                                                       ((NSAttributedString *)value).string.mutableCopy);
                    }
                } break;

// ... 省略很多

  default: break;
            }
        }
    } else {
        BOOL isNull = (value == (id)kCFNull);
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeObject: {
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
                } else if ([value isKindOfClass:[NSDictionary class]]) {
                    NSObject *one = nil;
                    if (meta->_getter) {
                        one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                    }
                    if (one) {
                        [one yy_modelSetWithDictionary:value];
                    } else {
                        Class cls = meta->_cls;
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary:value];
                            if (!cls) cls = meta->_genericCls; // for xcode code coverage
                        }
                        one = [cls new];
                        [one yy_modelSetWithDictionary:value];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                    }
                }
            } break;
                
            case YYEncodingTypeClass: {
                if (isNull) {
                    ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
                } else {
                    Class cls = nil;
                    if ([value isKindOfClass:[NSString class]]) {
                        cls = NSClassFromString(value);
                        if (cls) {
                            ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls);
                        }
                    } else {
                        cls = object_getClass(value);
                        if (cls) {
                            if (class_isMetaClass(cls)) {
                                ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value);
                            }
                        }
                    }
                }
            } break;
                
            case  YYEncodingTypeSEL: {
                if (isNull) {
                    ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL);
                } else if ([value isKindOfClass:[NSString class]]) {
                    SEL sel = NSSelectorFromString(value);
                    if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel);
                }
            } break;
                
            case YYEncodingTypeBlock: {
                if (isNull) {
                    ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL);
                } else if ([value isKindOfClass:YYNSBlockClass()]) {
                    ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value);
                }
            } break;
                
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion:
            case YYEncodingTypeCArray: {
                if ([value isKindOfClass:[NSValue class]]) {
                    const char *valueType = ((NSValue *)value).objCType;
                    const char *metaType = meta->_info.typeEncoding.UTF8String;
                    if (valueType && metaType && strcmp(valueType, metaType) == 0) {
                        [model setValue:value forKey:meta->_name];
                    }
                }
            } break;
                
            case YYEncodingTypePointer:
            case YYEncodingTypeCString: {
                if (isNull) {
                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
                } else if ([value isKindOfClass:[NSValue class]]) {
                    NSValue *nsValue = value;
                    if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) {
                        ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
                    }
                }
            } // break; commented for code coverage in next line
                
            default: break;
        }
    }
}

其它重要知识点

位操作

YYModel框架中定义了一个枚举 YYEncodingType ,这个类型的值可以保存property的值类型(Type Encoding);property的读写、原子、内存等属性(Property Encoding);方法的类型(Method Encoding)(暂时没用到)。在获取类型方法 YYEncodingGetType 获取类型的时候使用位活操作符"|"组合多个Encoding,在设置属性值的方法比如 ModelSetNumberToProperty ,使用位与操作符“&”判断是否是特定的类型。使用这种类型有两个好处: 1、节省空间;2、位操作效率比较高 ,比如乘除2,使用左移或者右移的效率会比乘除法高,不过实际编码中为了可读性还是最好使用乘除法,另外现代的编译器会帮我们做很多优化,在绝大多数的场景中,我们可以不用太关注这些细枝末节,真正需要优化的时候才考虑做这类代码上的优化。

信号和锁

YYModel中使用的是信号锁 dispatch_semaphore_t ,为什么使用这种类型的锁呢,可以从作者的博客中找到答案 不再安全的 OSSpinLock ,这种锁的效率还是非常高的,又可以避免OSSpinLock锁产生的问题。这个场景中 dispatch_semaphore_t 是一个读写互斥锁,CFDictionaryGetValue是读操作,CFDictionarySetValue是写操作,这两者同一时间只能一个进入,其余的需要等待。

+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef cache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    if (!meta || meta->_classInfo.needUpdate) {
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    return meta;
}

CF框架API

yy_modelSetWithDictionary 方法中使用到CF框架中容器遍历的API有 CFDictionaryApplyFunctionCFArrayApplyFunction ,一般滴使用CF框架的API性能会高于NSFoundation框架的API,因为NSFoundation框架的API是基于CF框架的API,此时NSFoundation框架相当于一个中间者,绕了一步。更多的关于CFArray的知识可以参考 Exposing NSMutableArray 这篇文章的介绍。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective JavaScript

Effective JavaScript

赫尔曼 (David Herman) / 黄博文、喻杨 / 机械工业出版社 / 2014-1-1 / CNY 49.00

Effective 系列丛书经典著作,亚马逊五星级畅销书,Ecma 的JavaScript 标准化委员会著名专家撰写,JavaScript 语言之父、Mozilla CTO —— Brendan Eich 作序鼎力推荐!作者凭借多年标准化委员会工作和实践经验,深刻辨析JavaScript 的内部运作机制、特性、陷阱和编程最佳实践,将它们高度浓缩为极具实践指导意义的 68 条精华建议。 本书共......一起来看看 《Effective JavaScript》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换