iOS 阅读YYModel

热门标签

特别声明:文章多为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

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

转换后的结果如下:

iOS 阅读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

转换后的结果如下:

iOS 阅读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)不会被赋值

iOS 阅读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)才会被赋值

iOS 阅读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

反序列化的结果如下:
 iOS 阅读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,基本的规则解释如下:

  • 类型 p.name == 'T',需要获取 p.value值,然后查询Type Encodings表格
  • 对应的ivar名称 p.name == 'V',需要获取 p.value值,p.value值为ivar名称
  • 读写属性 p.name == 'R' -> readonly
  • 内存属性 p.name == 'C' -> copy; p.name == '&' -> retain; p.name == 'W' -> weak
  • 原子属性 p.name == 'N' -> nonatomic
  • getter/setter属性 p.name = 'G<name>' -> G后面的内容为自定义的getter方法名称,使用p.value获取;p.name = 'S<name>' -> S后面的内容为自定义的setter方法名称,使用p.value获取

以下是一段使用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
// ...

流程时序图如下所示:

iOS 阅读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;
}

配置信息的获取

配置信息的获取主要流程时序图如下:
 iOS 阅读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;
}

模型对象的转换

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

iOS 阅读YYModel

入口函数yy_modelSetWithDictionary的功能如下:

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

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

- (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深度遍历json数据取值
  • YYValueForKeyPath广度遍历json数据取值
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这篇文章的介绍。

标签:

未经允许不得转载:作者:SheaYang, 转载或复制请以 超链接形式 并注明出处 技术Dog|博客
原文地址:《iOS 阅读YYModel》 发布于2019-09-29

分享到:
赞(0) 打赏

评论 抢沙发

6 + 2 =


iOS 阅读YYModel

长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.0主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?