YYModel设计原理

iOS开发中少不了各种各样的模型,不论是采用MVC、MVP还是MVVM设计模式都逃不过Model。而YYModel恰好是一款非常轻量级的Json转Model的库,代码风格良好且思路清晰。难能可贵的是在轻量级的代码下还保留着自动类型转换、类型安全、无侵入等特性,并且具有接近手写解析代码的超高性能。今天,咱们主要对这款三方库进行剖析,学习别人先进的设计理念。

本期要点:

  1. YYModel代码结构
  2. 功能介绍
  3. Model映射流程

YYModel代码结构

1305302-20180914153149933-1445616001.jpg

以上是YYModel的代码结构,整体结构非常简单明了。

功能介绍

  • YYClassInfo功能主要是将Runtime层级中的一些结构体封装到NSObject中调用。
    1305302-20180915103000006-951413839.jpg
  • NSObject+YYModel功能是提供调用的接口以及实现具体的模型转换逻辑。

上面讲到YYClassInfo主要功能是将Runtime层级的结构体封装到NSObject层级以便调用。下面是YYClassInfo与Runtime层级对比:

1305302-20180915103817595-987812236.png

Model映射流程
接下来我们将按照\color{#FF0000}{modelWithDictionary} 作为调用调用入口,一步步分析YYModel是如何完成对Model对象映射的。(注:整个流程比较复杂,主要为了尽量详细的介绍全整个YYModel的功能)

首先我们查看字典映射类方法实现入口代码:

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    
    Class cls = [self class];
   //获取类元数据信息
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    NSObject *one = [cls new];
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

上面出现了一个\color{#FF0000}{YYModelMeta}对象的初始化,这个类是用来对\color{#FF0000}{YYClassInfo}对象进行封装的,我们可以理解为\color{#FF0000}{YYClassInfo}的元类。我们来看看它的结构是怎么样的。

@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;//类信息
    //通过字典存储属性元类信息
    NSDictionary *_mapper;
    //存储所有的属性元类信息
    NSArray *_allPropertyMetas;
    //存储所有属性元类信息对应的映射key
    NSArray *_keyPathPropertyMetas;
    //存储需要映射到多个key的属性元类信息
    NSArray *_multiKeysPropertyMetas;
    //存储映射key的数目,该数字和_mapper.count相同
    NSUInteger _keyMappedCount;
    //model类的枚举类型
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary; //是否即将进行字典转化为model
    BOOL _hasCustomTransformFromDictionary;//是否已经进行了字典转化为model
    BOOL _hasCustomTransformToDictionary;//是否需要将model转化为字典
    BOOL _hasCustomClassFromDictionary;//是否已经完成字典到类信息的转换
}

上面我们了解了\color{#FF0000}{YYModelMeta}对象的结构,接下来我们来看看它的实例化过程。

+ (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);//设置信号量为1
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//信号量减1,其他线程不可以进行操作
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);//完成缓存类元数据缓存信息获取,信号量+1,释放线程访问权限
    if (!meta || meta->_classInfo.needUpdate) { //如果类元数据不存在或者需要更新
        meta = [[_YYModelMeta alloc] initWithClass:cls];//直接初始化类元数据
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//获取锁使用权,信号量 -1
、          //存储初始化的类元数据信息到字典中
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);//存储完成,释放锁权限
        }
    }
    return meta;
}

这个类方法中采用了信号量来保证在完成获取缓存的类信息过程和存储类信息过程的线程安全。这里最重要的方法在类元数据的初始化方法 meta = [[_YYModelMeta alloc] initWithClass:cls]; 接下来,我们看看它内部是如何实现类元数据的初始化的,以下代码不重要的地方有所省略。

- (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];
        }
    }
    
    // 获取容器中存放的model类型映射,也是model中数组中存放的model类型映射
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        //获取需要数组中存放的model类以及对应的key值
        genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            //当key是字符串类型,并且数组中存放的类是一个类或者是字符类型,则初始化为类,存起来
            //这里主要是为了过滤乱写的一些映射关系
            [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;
            //这里通过属性信息,以及类信息,还有通过上面存放的数组内model和key关系
            //判断当前属性model是否存放在数组里面,如果是,则取出对应的类,进行属性元信息的获取
            _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];
/   //初始化存储所有属性元数据key的数组
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
   //初始化存放所有多个key需要对应同一个model的数组
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            //根据替换前key获取对应的属性元数据类
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            //因为需要替换key,所以需要删除原来的key
            [allPropertyMetas removeObjectForKey:propertyName];
            //当需要替换的key是NSString类型时,证明是需要改为一个key对应一个属性元数据类
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                //属性元数据类映射key替换为最新的key
                propertyMeta->_mappedToKey = mappedToKey;
               //用来处理替换key类型为 @"desc":@"ext.desc"类型的数据,ext.desc是ect类型model类型desc字段直接赋值给desc
                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;
                    //存储一对一替换key属性元数据类到数组
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
             /**检查是否有同样key对应多个属性,这里有一个小技巧,当mapper[mappedToKey]指向
              当前最新那个propertyMeta,前一个mapper[mappedToKey]被记录到了当前propertyMeta->_next
              里面去了,所以要读取到所有相同mappedToKey的propertyMeta是,只要mapper[mappedToKey],
              mapper[mappedToKey]->_next,不停遍历,直到nil即可。
                */
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
              //当一个属性对应多个不同json里面的key是,如@"sookID": @[@"id", @"ID", @"sook_id"]
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in ((NSArray *)mappedToKey)) {
                    if (![oneKey isKindOfClass:[NSString class]]) continue;
                    if (oneKey.length == 0) continue;
                    //这里也兼容@"desc":@"ext.desc"类型的字段替换
                    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;
                 //一个属性对应多个key的映射key全部存放于当前属性元数据类的_mappedToKeyArray数组中
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
、               //存储一对多替换key属性元数据类到数组
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    /**因为前面替换key那里已经从allPropertyMetas删除了需要替换的key以及对应的value,这里存放的是不
    需要替换key的属性元数据类映射字典,这里将他们也放到mapper中存放
    */
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    //这里将获取当的所有映射关系的mapper存储为全局变量
    if (mapper.count) _mapper = mapper;
    //将一对一替换key属性元数据类存储到全局数组
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
    //一对多替换key属性元数据类存储到全局数组
    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;
}

上面我们将类元数据类初始化方法做了简单的代码注解,听过注解可以很清晰的了解到整个流程,下面咱们来了解一下类元数据类初始化中类对象的初始化过程是怎么样的。

以下是类对象信息初始化调用入口

//类对象信息初始化
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];

接下来咱们再观察一下方法实现

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef classCache;
    static CFMutableDictionaryRef metaCache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    if (info && info->_needUpdate) {
        [info _update];//类信息更新
    }
    dispatch_semaphore_signal(lock);
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];//初始化
        if (info) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

这里实际上初始化过程跟咱们类元数据类的初始化方法实现一模一样。我们主要需要了解两个地方,第一个就是 [info _update] 类信息更新的实现,以及 info = [[YYClassInfo alloc] initWithClass:cls]; 类初始化实现过程。

首先,咱们先了解一下YYClassInfo初始化实现过程。

- (instancetype)initWithClass:(Class)cls {
    if (!cls) return nil;
    self = [super init];
    _cls = cls;
    _superCls = class_getSuperclass(cls);
    _isMeta = class_isMetaClass(cls);
    if (!_isMeta) {
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    _name = NSStringFromClass(cls);
    [self _update];

    _superClassInfo = [self.class classInfoWithClass:_superCls];
    return self;
}

通过这个方法我们可以了解到,这里是通过runtime方法获取到当前class以及superClass,以及当前class的元类,还有类名等信息,关于父类对象的类信息则是采用递归调用的形式来进行初始化的。并且咱们从这里观察到类对象的初始化方法中也调用了[self _update]; 来更新类信息。

咱们接着了解一下 [info _update] 方法的实现过程。

- (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);
    }
   //获取类的实例变量列表,并存储到全局字典中 
    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;
}

通过以上代码可知,update方法主要是对类对象的方法列表类和属性列表类以及实例变量列表类进行更新。

接下来我们对类方法列表实现进行讲解,首先我们了解一下方法列表类有哪些属性。

/**
 Method information.
 */
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;                  // 方法结构体
@property (nonatomic, strong, readonly) NSString *name;                 // 方法名
@property (nonatomic, assign, readonly) SEL sel;                        // 方法选择器
@property (nonatomic, assign, readonly) IMP imp;                        // 方法实现函数指针
@property (nonatomic, strong, readonly) NSString *typeEncoding;         // 方法编码类型
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   // 方法返回值编码类型
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; // 方法参数编码类型

/**
 Creates and returns a method info object.
 
 @param method method opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithMethod:(Method)method;

然后,咱们来看一下方法类实例化过程。

- (instancetype)initWithMethod:(Method)method {
    if (!method) return nil;
    self = [super init];
    _method = method;
    _sel = method_getName(method);
    _imp = method_getImplementation(method);
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    const char *typeEncoding = method_getTypeEncoding(method);
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
    }
    char *returnType = method_copyReturnType(method);
    if (returnType) {
        _returnTypeEncoding = [NSString stringWithUTF8String:returnType];
        free(returnType);
    }
    unsigned int argumentCount = method_getNumberOfArguments(method);
    if (argumentCount > 0) {
        NSMutableArray *argumentTypes = [NSMutableArray new];
        for (unsigned int i = 0; i < argumentCount; i++) {
            char *argumentType = method_copyArgumentType(method, i);
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;
    }
    return self;
}

整个方法实现非常简单,主要是获取方方法imp函数指针,获取方法选择器sel,获取方法类型,以及返回值类型、参数等信息,然后存储到方法对象属性里面。

同理,我们先来了解一下属性类中包含有哪些信息。

/**
 Property information.
 */
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; // 属性结构体
@property (nonatomic, strong, readonly) NSString *name;           // 属性名
@property (nonatomic, assign, readonly) YYEncodingType type;      // 属性枚举类型
@property (nonatomic, strong, readonly) NSString *typeEncoding;   // 属性编码类型
@property (nonatomic, strong, readonly) NSString *ivarName;       // 属性对应的变量名称
@property (nullable, nonatomic, assign, readonly) Class cls;      // 类
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; // 属性相关协议
@property (nonatomic, assign, readonly) SEL getter;               // getter方法选择器
@property (nonatomic, assign, readonly) SEL setter;               // setter方法选择器

/**
 Creates and returns a property info object.
 
 @param property property opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithProperty:(objc_property_t)property;

属性对象初始化方法实现。

 - (instancetype)initWithProperty:(objc_property_t)property {
    if (!property) return nil;
    self = [super init];
    _property = property;
 //获取属性名,并将其从C字符串转化为NSString类型
    const char *name = property_getName(property);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    
    YYEncodingType type = 0;
    unsigned int attrCount;
 // 获取属性对应的所有描述,即objc_property_attribute_t的结构体数组,包括原子性、数据类型、内存语义等
    objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
    for (unsigned int i = 0; i < attrCount; i++) {
   // objc_property_attribute_t的name为C字符串,name[0]表示获取第一个字符
        switch (attrs[i].name[0]) {
            case 'T': { // 这里将解析数据的类型,比如NSString、BOOL、NSArray等
                if (attrs[i].value) {
                    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
                    // 用于解析数据类型
                    type = YYEncodingGetType(attrs[i].value);
                    //接下为NSObject对象类型,并且数据类型存在以及类型编码也存在时进行如下解析
                    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
                       //用需要扫描的_typeEncoding字符串,初始化scanner对象
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
                       // 我们的目的是拿到NSString,所以需要更改scanner扫描的起始位置为字符N所在的位置
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
                        
                        /** 开始扫描_typeEncoding直到遇到字符”或者<,遇到了则返回YES,并将扫描到的字符串存入clsName。
                          需要注意的是NSCharacterSet其实设置的是停止扫描的字符,如果该字符出现在NSScanner扫描的起始位置,该方法会返回NO,clsName也会为null */
                        NSString *clsName = nil;
                        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
                            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
                        }
                        
                       //当上面返回NO,进行协议的捕获
                        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': {
               //获取自定义的getter方法
                type |= YYEncodingTypePropertyCustomGetter;
                if (attrs[i].value) {
                    _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } break;
            case 'S': { //获取自定义的setter方法
                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以及验证_setter、_getter是否已经获取到,如果没有自定义_setter或者_getter方法,就根据方法名生成_getter和_setter方法
    _type = type;
    if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }
    return self;
}

上面的实现中,非常有意思的是NSScanner类,这个类是用于在字符串中扫描指定的字符,尤其是把它们翻译/转换为数字和别的字符串。可以在创建NSScaner时指定它的string属性,然后scanner会按照你的要求从头到尾地扫描这个字符串的每个字符。对于查询字符串而言,这真是一个强大的类。

最后我们再来看一下实例变量类有哪些属性

/**
 Instance variable information.
 */
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              // 变量结构体
@property (nonatomic, strong, readonly) NSString *name;         // 变量名
@property (nonatomic, assign, readonly) ptrdiff_t offset;       // 变量偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; // 变量编码类型
@property (nonatomic, assign, readonly) YYEncodingType type;    // 变量枚举类型

/**
 Creates and returns an ivar info object.
 
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithIvar:(Ivar)ivar;

通过观察该类拥有的属性,感觉整个类特别简单,我们对其实现进行一探究竟。

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

果然,整个实例变量类实现非常简单。

在类对象的元数据类的 initWithClass:(Class)cls 初始化方法实现中,我们已经了解到了YYClassInfo的实例化方法 YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; '的实现过程,接下来我们需要了解一下属性对象(YYClassPropertyInfo)的元类(_YYModelPropertyMeta)的初始化过程。

我们先来了解一下属性元类对象中有哪些成员变量?

/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             // 属性名
    YYEncodingType _type;        // 属性编码类型
    YYEncodingNSType _nsType;    // 属性的Foundation类型
    BOOL _isCNumber;             // 是否是C语言的Number类型
    Class _cls;                  // 属性所属的class
    Class _genericCls;           // 属性为容器时比如NSDctionary、NSArray内部泛型类,存储的class类型,可能为nil
    SEL _getter;                 // getter方法,实例变量不能响应时为nil
    SEL _setter;                 // setter方法, 实例变量不能响应时为nil
    BOOL _isKVCCompatible;       // 是否可以通过键值编码KVC访问
    BOOL _isStructAvailableForKeyedArchiver; //是否为可通过键值归档的结构体
    BOOL _hasCustomClassFromDictionary; ///< 类或者泛型类实现了 +modelCustomClassForDictionary:方法,则为YES
    
    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      // 映射的key
    NSArray *_mappedToKeyPath;   // json中keyPath路劲映射到当前key,如ext.desc路径的值映射到desc属性
    NSArray *_mappedToKeyArray;  // json中多个key中第一个不为空的值映射到当前key,如果存在这种数组,则为nil
    YYClassPropertyInfo *_info;  // 属性信息
    _YYModelPropertyMeta *_next; // 如果有多个属性映射到同一个 key 则指向下一个模型属性元
}

通过上面的注解,咱们搞清楚了属性元类包含的内容有哪些以后,我们就可以来了解一下属性元类初始化的过程了。

我们先来看看属性元类的实例化入口


_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                    propertyInfo:propertyInfo
                                                                         generic:genericMapper[propertyInfo.name]];

以上代码入口参数 genericMapper[[propertyInfo.name]从类数据的元类对象初始化方法中我们了解到,它是用来获取容器类型属性中存放的数据类型。

接下来看方法的实现逻辑

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    
    // 支持伪协议,如果存在协议,并且有协议同名类存在,则设置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) {
      /*这里实际上是判断是否为自定义的对象类型,如果为系统的则能检测出来,如果是自定义的,则
       直接显示 YYEncodingTypeNSUnknown
      */
        meta->_nsType = YYClassGetNSType(propertyInfo.cls);
    } else {
        meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
    }
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
        /*
         It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
         */
        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]) {
            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 invalid type:
         long double
         pointer (such as SEL/CoreFoundation object)
         */
        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;
}

通过以上方法实现,我们了解了属性元类信息基本大部分都是从属性类中获取到,直接存储到元类中。

经过上面的方法已经获得了YYClassInfo的所有信息,接下来讲解model的映射方法实现。上面讲解过的部分直接省略掉。

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
   ......
    NSObject *one = [cls new];
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

通过上面的实现,我们可以了解到 NSObject+YYModel 中映射时是先初始化了model,然后调用 [one yy_modelSetWithDictionary:dictionary] 方法来进行YYClassInfo内容的赋值。

具体实现如下:

- (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;
    //实现了,这个协议方法则,dic使用协议返回的内容
    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);//需要映射的model
    context.dictionary = (__bridge void *)(dic);//映射所需要的数据
     //判断模型元键值映射数量与 JSON 所得字典的数量关系
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
       /*
         一般情况下他们的数量相等
         特殊情况比如有的属性元会映射字典中的多个key
         为字典中的每个键值对调用 ModelSetWithDictionaryFunction
         这句话是核心代码,一般情况下就是靠 ModelSetWithDictionaryFunction 
         通过字典设置模型,它会递归调用将字典中所有key全部映射到model中
       */
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        //判断模型中是否存在映射keyPath的属性元
        if (modelMeta->_keyPathPropertyMetas) {
            //为每个映射keyPath的属性元执行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        //判断模型中是否存在映射多个key的属性元
        if (modelMeta->_multiKeysPropertyMetas) {
            //为每个映射多个key的属性元执行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
       /*
         模型元键值映射数量少,则认为不存在映射多个key的属性元
         直接为modelMeta的每个属性元执行 ModelSetWithPropertyMetaArrayFunction
        */
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
     /*
      该接口用于当默认 JSON 转 Model 不适合模型对象时做额外的逻辑处理
      我们也可以用这个接口来验证模型转换的结果
      */
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

上面的设置上下文ModelSetContext,实际上是一个包含了类数据的元类对象,以及映射model,待转换的字典信息的结构体。

上面方法中比较重要的有两个函数,ModelSetWithDictionaryFunction() 以及 ModelSetWithPropertyMetaArrayFunction() 函数。接下来,我们分别对这两个函数进行单独的讲解。

首先讲一下 ModelSetWithDictionaryFunction() 的实现原理

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)];
    //以及待映射的model
    __unsafe_unretained id model = (__bridge id)(context->model);
   /*
    这里遍历属性元类,直到propertyMeta->_next= nil
    其主要原因是检查同一个key对应多个属性的情况
    */
    while (propertyMeta) {
        if (propertyMeta->_setter) {
           //如果该属性元类的setter方法存在,则调用下面函数,内部通过objc_msgSend(id,SEL,...)去实现
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

这个函数 ModelSetWithPropertyMetaArrayFunction() 实现原理如下

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;
    //如何属性元类存在JSON中多key对应model同一个key,则获取第一个不为nil的内容赋值给model对应的key
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
       //如果需要对keypath路径下的值设置给model的key,那么就通过以下函数去获取对应路径值
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
       //如果不属于以上两种情况,则直接取值
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
      //如果存在值,则直接调用下面函数,内部通过objc_msgSend(id,SEL,...)去实现
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

以上函数中 YYValueForMultiKeys() 和 YYValueForKeyPath() 实现原理都非常简单,这里就不做详细的赘述。

接下来我们讲解一下,ModelSetValueForProperty(); 函数中是如何调用runtime的 objc_msgSend(id,SEL,...) 函数来实现setter方法赋值的。

static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) {
    //如果属性是一个 CNumber,即输入 int、uint……
    if (meta->_isCNumber) {
        //转化为NSNumber,然后调用ModelSetNumberToProperty()函数封装的赋值NSNumber的操作赋值
        NSNumber *num = YYNSNumberCreateFromID(value);
        ModelSetNumberToProperty(model, num, meta);
        if (num != nil) [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) {
                //// 如果属性属于 nsType,即 NSString、NSNumber……
                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;
                    
                case YYEncodingTypeNSValue:
                case YYEncodingTypeNSNumber:
                case YYEncodingTypeNSDecimalNumber: {
                    if (meta->_nsType == YYEncodingTypeNSNumber) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value));
                    } else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) {
                        if ([value isKindOfClass:[NSDecimalNumber class]]) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else if ([value isKindOfClass:[NSNumber class]]) {
                            NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                        } else if ([value isKindOfClass:[NSString class]]) {
                            NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
                            NSDecimal dec = decNum.decimalValue;
                            if (dec._length == 0 && dec._isNegative) {
                                decNum = nil; // NaN
                            }
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                        }
                    } else { // YYEncodingTypeNSValue
                        if ([value isKindOfClass:[NSValue class]]) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSData:
                case YYEncodingTypeNSMutableData: {
                    if ([value isKindOfClass:[NSData class]]) {
                        if (meta->_nsType == YYEncodingTypeNSData) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else {
                            NSMutableData *data = ((NSData *)value).mutableCopy;
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                        }
                    } else if ([value isKindOfClass:[NSString class]]) {
                        NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
                        if (meta->_nsType == YYEncodingTypeNSMutableData) {
                            data = ((NSData *)data).mutableCopy;
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                    }
                } break;
                    
                case YYEncodingTypeNSDate: {
                    if ([value isKindOfClass:[NSDate class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else if ([value isKindOfClass:[NSString class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value));
                    }
                } break;
                    
                case YYEncodingTypeNSURL: {
                    if ([value isKindOfClass:[NSURL class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else if ([value isKindOfClass:[NSString class]]) {
                        NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
                        NSString *str = [value stringByTrimmingCharactersInSet:set];
                        if (str.length == 0) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSArray:
                case YYEncodingTypeNSMutableArray: {

                    if (meta->_genericCls) {//对属性元的泛型判断
                        NSArray *valueArr = nil;
                        if ([value isKindOfClass:[NSArray class]]) valueArr = value;
                        else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
                        if (valueArr) {
                            NSMutableArray *objectArr = [NSMutableArray new];
                            for (id one in valueArr) {
                               //如果获取到的是一个泛型类对象,则直接加入数组
                                if ([one isKindOfClass:meta->_genericCls]) {
                                    [objectArr addObject:one];
                                } else if ([one isKindOfClass:[NSDictionary class]]) {
                                  /*
                                   正常情况获取到的是字典,则直接拿到容器对应的类的实例对象进行映射
                                   这里有意思的是这里又是调用当前前面的那个映射方法
                                     */
                                    Class cls = meta->_genericCls;
                                    if (meta->_hasCustomClassFromDictionary) {
                                        cls = [cls modelCustomClassForDictionary:one];
                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                    }
                                    NSObject *newOne = [cls new];
                                    [newOne yy_modelSetWithDictionary:one];
                                    if (newOne) [objectArr addObject:newOne];
                                }
                            }
                            /*
                             映射得到的结果,设置给setter,此方法对应类型
                             @property (nonatomic, copy) NSArray<ItemModel*> *items;的属性
                             */
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
                        }
                    } else {
                        if ([value isKindOfClass:[NSArray class]]) {
                            if (meta->_nsType == YYEncodingTypeNSArray) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                               meta->_setter,
                                                                               ((NSArray *)value).mutableCopy);
                            }
                        } else if ([value isKindOfClass:[NSSet class]]) {
                            if (meta->_nsType == YYEncodingTypeNSArray) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                               meta->_setter,
                                                                               ((NSSet *)value).allObjects.mutableCopy);
                            }
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSDictionary:
                case YYEncodingTypeNSMutableDictionary: {
                    if ([value isKindOfClass:[NSDictionary class]]) {
                        if (meta->_genericCls) {
                            NSMutableDictionary *dic = [NSMutableDictionary new];
                            [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) {
                                if ([oneValue isKindOfClass:[NSDictionary class]]) {
                                    Class cls = meta->_genericCls;
                                    if (meta->_hasCustomClassFromDictionary) {
                                        cls = [cls modelCustomClassForDictionary:oneValue];
                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                    }
                                    NSObject *newOne = [cls new];
                                    [newOne yy_modelSetWithDictionary:(id)oneValue];
                                    if (newOne) dic[oneKey] = newOne;
                                }
                            }];
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
                        } else {
                            if (meta->_nsType == YYEncodingTypeNSDictionary) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                            } else {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                               meta->_setter,
                                                                               ((NSDictionary *)value).mutableCopy);
                            }
                        }
                    }
                } break;
                    
                case YYEncodingTypeNSSet:
                case YYEncodingTypeNSMutableSet: {
                    NSSet *valueSet = nil;
                    if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value];
                    else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value);
                    
                    if (meta->_genericCls) {
                        NSMutableSet *set = [NSMutableSet new];
                        for (id one in valueSet) {
                            if ([one isKindOfClass:meta->_genericCls]) {
                                [set addObject:one];
                            } else if ([one isKindOfClass:[NSDictionary class]]) {
                                Class cls = meta->_genericCls;
                                if (meta->_hasCustomClassFromDictionary) {
                                    cls = [cls modelCustomClassForDictionary:one];
                                    if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                }
                                NSObject *newOne = [cls new];
                                [newOne yy_modelSetWithDictionary:one];
                                if (newOne) [set addObject:newOne];
                            }
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
                    } else {
                        if (meta->_nsType == YYEncodingTypeNSSet) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                           meta->_setter,
                                                                           ((NSSet *)valueSet).mutableCopy);
                        }
                    }
                } // break; commented for code coverage in next line
                    
                default: break;
            }
        }
    } else {
        BOOL isNull = (value == (id)kCFNull);
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeObject: {
                Class cls = meta->_genericCls ?: meta->_cls;
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:cls] || !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 {
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary:value] ?: cls;
                        }
                        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;
        }
    }
}

以上代码,虽然逻辑长了一点,但是总的来说逻辑还是很简单的。总结起来就是根据可能出现的类型做出对应的逻辑操作。

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

推荐阅读更多精彩内容