YYModel源码分析(二)

下面分析NSObject+YYModel的实现,从头文件可以看该类由三个Category和一个Protocol所组成,其主要的实现集中在NSObject的Category中。

_YYModelPropertyMeta

在其.m文件从上向下查看到的第一个类为_YYModelPropertyMeta,实例化方法如下:

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    
    // 泛型类不存在且有遵守的协议,支持带协议名的伪泛型类
    if (!generic && propertyInfo.protocols) {
        for (NSString *protocol in propertyInfo.protocols) {
            Class cls = objc_getClass(protocol.UTF8String);
            if (cls) {
                generic = cls;
                break;
            }
        }
    }
    
    _YYModelPropertyMeta *meta = [self new];
    // 属性名
    meta->_name = propertyInfo.name;
    // 属性类型
    meta->_type = propertyInfo.type;
    // 属性信息
    meta->_info = propertyInfo;
    // 泛型类
    meta->_genericCls = generic;
    // 属性类型为对象类型
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
        // 属性的Foundation类型
        meta->_nsType = YYClassGetNSType(propertyInfo.cls);
    } else {
        // 是否为C数字类型
        meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
    }
    // 结构体类型的属性
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {

        // NSKeyedUnarchiver不能decode除了下面这些结构体
        static NSSet *types = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSMutableSet *set = [NSMutableSet new];
            // 32 bit
            [set addObject:@"{CGSize=ff}"];
            [set addObject:@"{CGPoint=ff}"];
            [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
            [set addObject:@"{CGAffineTransform=ffffff}"];
            [set addObject:@"{UIEdgeInsets=ffff}"];
            [set addObject:@"{UIOffset=ff}"];
            // 64 bit
            [set addObject:@"{CGSize=dd}"];
            [set addObject:@"{CGPoint=dd}"];
            [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
            [set addObject:@"{CGAffineTransform=dddddd}"];
            [set addObject:@"{UIEdgeInsets=dddd}"];
            [set addObject:@"{UIOffset=dd}"];
            types = set;
        });
        // 属性类型的编码值属于上面的结构体
        if ([types containsObject:propertyInfo.typeEncoding]) {
            // 能够archiver的结构体
            meta->_isStructAvailableForKeyedArchiver = YES;
        }
    }
    // 属性类
    meta->_cls = propertyInfo.cls;
    
    if (generic) {
        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
    } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    }
    // 取值方法
    if (propertyInfo.getter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    // 设值方法
    if (propertyInfo.setter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
            meta->_setter = propertyInfo.setter;
        }
    }
    
    if (meta->_getter && meta->_setter) {
        // 不能KVC的类型
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeBool:
            case YYEncodingTypeInt8:
            case YYEncodingTypeUInt8:
            case YYEncodingTypeInt16:
            case YYEncodingTypeUInt16:
            case YYEncodingTypeInt32:
            case YYEncodingTypeUInt32:
            case YYEncodingTypeInt64:
            case YYEncodingTypeUInt64:
            case YYEncodingTypeFloat:
            case YYEncodingTypeDouble:
            case YYEncodingTypeObject:
            case YYEncodingTypeClass:
            case YYEncodingTypeBlock:
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion: {
                meta->_isKVCCompatible = YES;
            } break;
            default: break;
        }
    }
    
    return meta;
}

大致就是将YYClassPropertyInfo中的属性作了一个转换,获取属性的Foundation和是否为C数字的函数较为简单,在实现文件中,所有的函数前都加了force_inline,这是作者定义的宏。

#define force_inline __inline__ __attribute__((always_inline))

这个宏的意思是这样的:

attribute((always_inline))的意思是强制内联,所有加了attribute((always_inline))的函数再被调用时不会被编译成函数调用而是直接扩展到调用函数体内,比如我定义了函数
attribute((always_inline)) void a()和
void b()
{
a();
}
b调用a函数的汇编代码不会是跳转到a执行,而是a函数的代码直接在b内成为b的一部分。

也可上Stack Overflow查看更专业的回答。

YYModel Protocol

协议包含了八个可选实现方法,八个方法的使用在NSObject+YYModel.h中有详细描述,这里只是简单介绍一下。

  • + (NSDictionary *)modelCustomPropertyMapper
    定制model的属性与设置值的对应关系;
  • + (NSDictionary *)modelContainerPropertyGenericClass
    Model的容器类属性,如:NSDictionary, NSArray, NSSet等对应的泛型类;
  • + (Class)modelCustomClassForDictionary:(NSDictionary *)dictionary
    在JSON -> Model的转换中,用该方法选择基于字典数据的自定义类;
  • + (NSArray *)modelPropertyBlacklist
    model属性黑名单,位于黑名单中的属性将不会被赋值,返回nil忽略此特性;
  • + (NSArray *)modelPropertyWhitelist
    model属性白名单,不位于白名单中的属性将不会被赋值,返回nil忽略此特性;
  • - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic
    如果Model实现了此方法,它将会在+modelWithJSON: +modelWithDictionary: -modelSetWithJSON: -modelSetWithDictionary:之前调用,如果该方法返回空,model将会忽略;
  • - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic
    如果默认的json-to-model不适合model对象,可以实现此方法,会在+modelWithJSON: +modelWithDictionary: -modelSetWithJSON: -modelSetWithDictionary:之后调用;返回YES表明此model可用,返回NO忽略此model;
  • - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic
    如果默认的model-to-json不适合model类,可以实现此方法,会在-modelToJSONObject -modelToJSONString之后调用;返回YES表明此model可用,返回NO忽略此model;

_YYModelMeta

不多说,直接上源码开始分析:

- (instancetype)initWithClass:(Class)cls {
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];
    
    // 黑名单列表
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    
    // 白名单列表
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    
    // 容器类属性的泛型类
    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;
        }
    }
    
    // 创建所有属性的元信息
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    // 递归解析父类,忽略根类(NSObject/NSProxy)
    while (curClassInfo && curClassInfo.superCls != nil) {
        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;
    
    // 映射关系表
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    // keyPath -> model property 映射数组
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    // 多个key -> model property 映射数组
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        // 定制的 key -> property 映射关系表
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            // 取出属性元信息
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            // 遍历一个删除一个
            [allPropertyMetas removeObjectForKey:propertyName];
            // key为字符串的情况,还有一种为数组,即多个key对应一个property
            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];
                }
                // 多个property对应一个key,使用_next串起来
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                // key 数组
                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];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    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;
}
  1. 查看有没有实现黑名单和白名单列表,如果有的话记录下来;
  2. 容器类属性的泛型类要求,处理好记录下来;
  3. model类所有的属性元信息,包括父类,忽略根类(NSObject/NSProxy);
  4. 定制的key->property映射关系;
  5. 其他简单的赋值处理。

对于定制的key->property映射关系稍微讲解一下,例如对下面的model类来说:

@interface YYBook : NSObject<YYModel>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *desc;
@property (nonatomic, copy) NSString *bookID;
@property (nonatomic, assign) NSInteger page;

@end

对应的返回json数据如下:

{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowling."
    },
    "ID" : 100010
}

默认情况下key和property是相同名称,一一对应关系的。如上的情况,返回的字典的key与model的属性名不相同这样赋值的时由于找不到model对应的property会导致赋值失败,所以我们需要定制映射关系。

+ (NSDictionary<NSString *,id> *)modelCustomPropertyMapper {
    return @{@"name"  : @"n",
             @"page"  : @"p",
             @"desc"  : @"ext.desc",
             @"bookID": @[@"id", @"ID", @"book.id"]};
}

model类遵守YYModel协议,实现上述方法,返回key->property映射关系字典。
YYModel在处理映射关系字典时分了几种情况:

  1. key直接对应property;
  2. keyPath对应property,这种情况下会进行 .分割、去空字符处理,将分割后的数组记录下来用于后续赋值操作;
  3. property对应多个key。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容