《YYModel源碼分析(二)NSObject+YYModel》

承接上文《YYModel源碼分析(一)YYClassInfo》
之前文章講述了YYClassInfo如何將runtime類結(jié)構(gòu)封裝到OC層躺同。這篇文章主要講述YYModel是如何用NSObject分類疚脐,實現(xiàn)非侵入式j(luò)son-model的(類型轉(zhuǎn)換矾策,容錯忙上,model轉(zhuǎn)json會在其他文章中討論)邀层。

寫在開頭

NSObject+ YYModel中并不只有NSObject分類消痛,還包含了_YYModelPropertyMeta_YYModelMeta以及協(xié)議<YYModel>讹剔,當(dāng)然又聲明了很多靜態(tài)(內(nèi)聯(lián))函數(shù),至于為什么用內(nèi)聯(lián)函數(shù)而不用類方法或者宏定義蜈出,是因為內(nèi)聯(lián)函數(shù)在編譯中會將代碼插入到調(diào)用的位置田弥,這樣會提高調(diào)用效率,相對于宏又有函數(shù)的特點铡原。具體可以看這里《IOS 內(nèi)聯(lián)函數(shù)Q&A》偷厦。

<YYModel>協(xié)議

首先字典轉(zhuǎn)模型商叹,就是字典中key對應(yīng)的value賦值給model對應(yīng)的屬性的過程,默認(rèn)情況下我們都會將屬性名對應(yīng)成字典的key只泼,那么如果我們不想這么起名字剖笙。或者我們有這樣一個json:

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

我們想賦值給這個model

@interface YYBook : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end

要實現(xiàn)以上的需求就必須告訴YYModel屬性應(yīng)該如何取值请唱,<YYModel>提供了這樣一套規(guī)范協(xié)議弥咪。接下來我們依次看一下

/**
 返回一個map,key是屬性名十绑,value是json中對應(yīng)的key聚至,可以有三種形式。
 
 @{@"name"  : @"n",                         //對應(yīng)一個json中的key
   @"desc"  : @"ext.desc",                  //對應(yīng)一個json地址本橙。
   @"bookID": @[@"id", @"ID", @"book_id"]}; //對應(yīng)多個json中的key扳躬。
 */
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
/**
 告訴YYModel容器類型中元素的類型。如下:
 @{@"shadows" : [YYShadow class],
   @"borders" : YYBorder.class,
   @"attachments" : @"YYAttachment" }
 value可以穿Class也可以穿字符串勋功,可以自動解析
 */
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
/**
想根據(jù)dictionary提供的數(shù)據(jù)創(chuàng)建不同的類坦报,實現(xiàn)這個方法库说,會根據(jù)返回的類型創(chuàng)建對象
注意這個協(xié)議對`+modelWithJSON:`, `+modelWithDictionary:`,這兩個方法有效
 */
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
/**
 在json轉(zhuǎn)model的時候狂鞋,黑名單上的屬性都會被忽略
 */
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
/**
 在json轉(zhuǎn)model的時候,如果屬性沒有在白名單上潜的,將會被忽略骚揍。
 */
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
/**
 這個方法可以在json轉(zhuǎn)model之前對dic進(jìn)行更改,json轉(zhuǎn)model將按照返回的dic為準(zhǔn)啰挪。
 */
- (NSDictionary *)modelCustomWillTransformDictionary:(NSDictionary *)dic;
/**
 該接口會在json轉(zhuǎn)model之后調(diào)用信不,用于不適合模型對象時做額外的邏輯處理。我們也可以用這個接口來驗證模型轉(zhuǎn)換的結(jié)果
 */
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
靜態(tài)函數(shù)

在NSObject+YYModel.m文件中一看亡呵,差不多一半都是靜態(tài)(內(nèi)聯(lián))函數(shù)抽活,內(nèi)聯(lián)函數(shù)我們前面已經(jīng)說過了,static修飾函數(shù)跟普通函數(shù)有以下區(qū)別:

  • 語法與C++保持一致锰什,只在模塊內(nèi)部可見
  • 跟類無關(guān)下硕,所以也無法調(diào)用self,只能根據(jù)參數(shù)實現(xiàn)相關(guān)功能
  • 靜態(tài)參數(shù)不參與動態(tài)派發(fā)汁胆,沒有再函數(shù)列表里梭姓,靜態(tài)綁定
    所以因為要頻繁調(diào)用,所以尋求更高效的static函數(shù)嫩码。我把靜態(tài)函數(shù)和其功能都列在下面了誉尖,供參考。
//將類解析成Foundation類型铸题,傳入Class返回枚舉YYEncodingNSType
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) 
//通過YYEncodingType判斷是否是c數(shù)字類型
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type)
//將一個ID類型的數(shù)據(jù)解析成NSNumber铡恕,這里主要處理了字符串轉(zhuǎn)數(shù)字的情況
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value)
//NSString類型數(shù)據(jù)轉(zhuǎn)NSDate琢感,這里幾乎兼容了所有時間格式,并且做了容錯
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string)
//獲取NSBlock這個類探熔,加入了打印我們可以看出 block 的父類的關(guān)系是block -------> NSGlobalBlock ---------> NSBlock
static force_inline Class YYNSBlockClass() 
//獲取ISO時間格式
static force_inline NSDateFormatter *YYISODateFormatter()
//根據(jù)KeyPath獲取一個字典中的數(shù)據(jù)
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) 
//一句多個Key從字典中獲取數(shù)據(jù)猩谊,這里如果有一個Key有值就取值返回。
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) 
//
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,
                                                            __unsafe_unretained _YYModelPropertyMeta *meta)
//為一個對象設(shè)置數(shù)值屬性
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
                                                  __unsafe_unretained NSNumber *num,
                                                  __unsafe_unretained _YYModelPropertyMeta *meta)
//為對象的屬性賦值
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta)
//通過鍵值為_context設(shè)置屬性祭刚,_context是一個結(jié)構(gòu)體牌捷,后面我們會講到,包含了數(shù)據(jù)源dic涡驮、model和_YYModelMeta暗甥。
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context)
//為對象的_propertyMeta屬性賦值。
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) 
//由model返回一個有效的json捉捅。
static id ModelToJSONObjectRecursive(NSObject *model) 

關(guān)于這些方法的實現(xiàn)撤防,后面用到會細(xì)說。

_YYModelPropertyMeta

其實_YYModelPropertyMeta類型是在YYClassPropertyInfo的基礎(chǔ)上的進(jìn)一步解析并且關(guān)聯(lián)了從<YYModel>協(xié)議中的取值信息棒口。

/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< 屬性名
    YYEncodingType _type;        ///< 屬性類型寄月,OC類型統(tǒng)一為YYEncodingTypeObject
    YYEncodingNSType _nsType;    ///< 屬性的Foundation類型,NSString等等无牵。
    BOOL _isCNumber;             ///< 是否是c數(shù)字類型
    Class _cls;                  ///< 屬性類型漾肮,
    Class _genericCls;           ///< 如果是容器類型,是容器類型內(nèi)元素的類型茎毁,如果不是容器類型為nil克懊。
    SEL _getter;                 ///< getter方法
    SEL _setter;                 ///< setter方法
    BOOL _isKVCCompatible;       ///< 是否可以使用KVC
    BOOL _isStructAvailableForKeyedArchiver; ///< 結(jié)構(gòu)體是否支持歸檔解擋
    BOOL _hasCustomClassFromDictionary; ///< 是否實現(xiàn)了 +modelCustomClassForDictionary:協(xié)議
    
    NSString *_mappedToKey;      ///< 表明該屬性取數(shù)據(jù)源中_mappedToKey對應(yīng)的value的值。
    NSArray *_mappedToKeyPath;   ///< 表明該屬性取數(shù)據(jù)源中_mappedToKeyPath對應(yīng)路徑的value值七蜘,如果為nil說明沒有關(guān)鍵路徑
    NSArray *_mappedToKeyArray;  ///< key或者keyPath的數(shù)組褒繁,表明可從多個key中取值翰蠢。
    YYClassPropertyInfo *_info;  ///< 屬性信息
    _YYModelPropertyMeta *_next; ///< 下一個元數(shù)據(jù)匆绣,如果有多個屬性映射到同一個鍵榜跌。
}
@end

_YYModelPropertyMeta屬性我們可以看出,如果屬性是Foundation類型碧库,會被解析成具體的OC類型柜与,用枚舉的形式存儲在_nstype中,同時由Model實現(xiàn)的<YYModel>協(xié)議可以獲取到取值信息_mappedToKey谈为、_mappedToKeyPath _mappedToKeyArray信息旅挤,這個在之后的賦值操作中起著至關(guān)重要的作用。

@implementation _YYModelPropertyMeta

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    // 這里有些許疑惑伞鲫,generic是當(dāng)屬性是容器類時粘茄,容器類中包含的元素,代碼邏輯是如果generic為空,且propertyInfo.protocols不為空柒瓣,如果propertyInfo.protocols中的元素是Class的時候?qū)⒋薱lass賦值給generic儒搭,但是propertyInfo.protocols確實存儲的是協(xié)議,propertyInfo.protocols的解析過程是取objc_property_attribute_t中<>中的字符芙贫,但是經(jīng)測試只有一個屬性遵循了某種協(xié)議才會出現(xiàn)<>字符搂鲫,NSSArray<NSString*> *這樣的屬性編碼字符串也是@"NSSArray",所以這塊貌似沒什么用磺平。
    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的成員變量賦值
    meta->_name = propertyInfo.name;
    //類型枚舉
    meta->_type = propertyInfo.type;
    //存儲屬性元數(shù)據(jù)
    meta->_info = propertyInfo;
    //容器類包含的通用類型
    meta->_genericCls = generic;
    //如果屬性是OC類型的
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
        //解析成枚舉
        meta->_nsType = YYClassGetNSType(propertyInfo.cls);
    } else {
        //判斷是否是number類
        meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
    }
    //如果是結(jié)構(gòu)圖
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
        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;
        });
        //如果是以上結(jié)構(gòu)體則支持歸解檔
        if ([types containsObject:propertyInfo.typeEncoding]) {
            meta->_isStructAvailableForKeyedArchiver = YES;
        }
    }
    meta->_cls = propertyInfo.cls;
    
    if (generic) {
        //容器類元素是否實現(xiàn)了 modelCustomClassForDictionary協(xié)議
        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
    } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    }
    
    //設(shè)置getter方法
    if (propertyInfo.getter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    //設(shè)置setter方法
    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;
}
@end
_YYModelMeta

_YYModelMeta通過Model遵循的<YYModel>協(xié)議魂仍,收集取值信息,并映射到_YYModelPropertyMeta當(dāng)中拣挪,將其中有效的信息封裝到該類中擦酌。

@interface _YYModelMeta : NSObject {
    //@package當(dāng)前framework可以使用,外部不可以
    @package
    
    YYClassInfo *_classInfo;
    /// [key:_YYModelPropertyMeta]
    NSDictionary *_mapper;
    /// 所有的屬性_YYModelPropertyMeta數(shù)據(jù)菠劝,這里包含當(dāng)前類到跟類NSObject中的所有屬性
    NSArray *_allPropertyMetas;
    /// 映射到KeyPath的屬性_keyPathPropertyMetas集合
    NSArray *_keyPathPropertyMetas;
    /// 映射到多個鍵值的屬性_keyPathPropertyMetas集合
    NSArray *_multiKeysPropertyMetas;
    /// 屬性映射的數(shù)量赊舶。
    NSUInteger _keyMappedCount;
    /// Foundation類型
    YYEncodingNSType _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}
@end

接下來討論一下_YYModelMet是如何初始化的。過程如下

  • 1.從實現(xiàn)的modelPropertyBlacklist赶诊、modelPropertyWhitelist協(xié)議中獲取取值黑名單笼平、白名單。
  • 2.從實現(xiàn)的modelContainerPropertyGenericClass協(xié)議中獲取容器類屬性中的元素類型
  • 3.獲取當(dāng)前類及繼承鏈直至NSObject中所有的屬性生成_YYModelPropertyMeta對象舔痪,存儲到allPropertyMetas
  • 4.從實現(xiàn)的modelCustomPropertyMapper協(xié)議中獲取自定義map寓调,這里map的key是屬性名,value有三種情況辙喂,第一是對應(yīng)一個取值key捶牢,第二是一個keypath用'.'隔開鸠珠,第三是一個字符數(shù)組對應(yīng)多個取值key
  • 5.遍歷map巍耗,由mapkey取出對應(yīng)的propertyMeta然后根據(jù)步驟4中value的三種情況給propertyMeta_mappedToKey、_mappedToKeyPath渐排、_mappedToKeyArray賦值炬太,這樣就把屬性和取值邏輯綁定在了一起
  • 6.給_keyMappedCount賦值,查看modelCustomWillTransformFromDictionary驯耻、modelCustomTransformFromDictionary亲族、modelCustomTransformToDictionary 、modelCustomClassForDictionary這四個協(xié)議是否實現(xiàn)可缚。

這個過程代碼比較多霎迫,就不列出來了。感興趣的可以自己看下哈帘靡。

NSObject (YYModel)

NSObject (YYModel)是YYModel非侵入式的關(guān)鍵知给,模型對象通過調(diào)用擴(kuò)展方法實現(xiàn)json轉(zhuǎn)model。接下來我們用json-model的核心方法yy_modelWithDictionary舉例。

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    //容錯處理
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    //獲取當(dāng)前類的類型
    Class cls = [self class];
    //創(chuàng)建_YYModelMeta
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    //這里創(chuàng)建_YYModelMeta的目的就是查看是否實現(xiàn)了modelCustomClassForDictionary協(xié)議涩赢,哈哈戈次,這里回溯一下modelCustomClassForDictionary的功能,這個協(xié)議你可以根據(jù)dictionary數(shù)據(jù)創(chuàng)建一個不同于當(dāng)前類的對象來完成json轉(zhuǎn)model筒扒。
    if (modelMeta->_hasCustomClassFromDictionary) {
        //如果實現(xiàn)了這個協(xié)議則替換當(dāng)前類型怯邪。
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    //由獲取到的類型創(chuàng)建對象
    NSObject *one = [cls new];
    //調(diào)用yy_modelSetWithDictionary方法。
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

再看一下屬性賦值的方法yy_modelSetWithDictionary

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    //容錯處理
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    //創(chuàng)建_YYModelMeta
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    //查看是否實現(xiàn)modelCustomWillTransformFromDictionary協(xié)議花墩,如果實現(xiàn)調(diào)用該方法悬秉,處理dic
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    //創(chuàng)建ModelSetContext,一個結(jié)構(gòu)體
    //    typedef struct {
    //        void *modelMeta;  ///< _YYModelMeta
    //        void *model;      ///< id (self)
    //        void *dictionary; ///< NSDictionary (json)
    //    } ModelSetContext;
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
        //如果自定義的鍵值數(shù)量大于等于數(shù)據(jù)源的鍵值數(shù)量冰蘑,那么按照自定義鍵值處理
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        //CFDictionaryApplyFunction意思是為字典中的每個鍵值對調(diào)用一次函數(shù)
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            //處理取值為_keyPathPropertyMetas形式的屬性
            //CFArrayApplyFunction是為數(shù)組中的每個元素對調(diào)用一次函數(shù)搂捧。
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            //處理取值為_multiKeysPropertyMetas形式的屬性
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        //如果自定義鍵值數(shù)量小于數(shù)據(jù)源的鍵值數(shù)量,那么直接按照dic key值給屬性賦值懂缕,自定義的無效
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

通過以上代碼邏輯我們知道允跑,如果沒有設(shè)置全量鍵值映射,也就是說實際數(shù)據(jù)源的鍵值數(shù)量大于自定義鍵值數(shù)量搪柑,那么自定義鍵值無效聋丝,會直接按照實際數(shù)據(jù)源的key對應(yīng)屬性名進(jìn)行賦值。

我們可以看到賦值操作中有兩個比較重要的方法ModelSetWithDictionaryFunction,ModelSetWithPropertyMetaArrayFunction

/**
 通過鍵值給模型賦值
 
 @param _key     鍵
 @param _value   值
 @param _context 賦值必要的數(shù)據(jù)工碾,model弱睦,modelMeta,dictionary
 */
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    //通過key取到響應(yīng)的屬性
    __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;
    };
}
/**
 為模型的某一個屬性賦值
 
 @param _propertyMeta 屬性
 @param _context   賦值必要的數(shù)據(jù)渊额,model况木,modelMeta,dictionary
 */
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);
    }
}

可以看到這兩個方法同歸旬迹,在取到值之后都調(diào)用了ModelSetValueForProperty的方法火惊,這個才是真正屬性賦值的方法。這個函數(shù)做的就是通過runtime函數(shù)objc_msgSend調(diào)用對象的setter方法賦值奔垦,之所以代碼量巨大是因為對所有的數(shù)據(jù)類型(c數(shù)字屹耐,foundation類型)做了判斷并添加了大量的容錯。關(guān)于類型轉(zhuǎn)換和容錯之后會單獨出一篇文章談?wù)摗?/p>

總結(jié)

  • YYModel通過擴(kuò)展實現(xiàn)了無侵入式操作
  • <YYModel>協(xié)議使Model與YYModel進(jìn)行數(shù)據(jù)交互
  • YYClassInfo封裝Model類型的runtime數(shù)據(jù)
  • _YYModelPropertyMeta將屬性與取值信息綁定
  • _YYModelMeta封裝所有的_YYModelPropertyMeta屬性
  • 最后通過runtime接口調(diào)用屬性對應(yīng)的setter方法賦值
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椿猎,一起剝皮案震驚了整個濱河市惶岭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌犯眠,老刑警劉巖按灶,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筐咧,居然都是意外死亡鸯旁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羡亩,“玉大人摩疑,你說我怎么就攤上這事∥访” “怎么了雷袋?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辞居。 經(jīng)常有香客問我楷怒,道長,這世上最難降的妖魔是什么瓦灶? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任鸠删,我火速辦了婚禮,結(jié)果婚禮上贼陶,老公的妹妹穿的比我還像新娘刃泡。我一直安慰自己,他們只是感情好碉怔,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布烘贴。 她就那樣靜靜地躺著,像睡著了一般撮胧。 火紅的嫁衣襯著肌膚如雪桨踪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天芹啥,我揣著相機(jī)與錄音锻离,去河邊找鬼。 笑死墓怀,一個胖子當(dāng)著我的面吹牛汽纠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捺疼,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼疏虫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啤呼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤呢袱,失蹤者是張志新(化名)和其女友劉穎官扣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羞福,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡惕蹄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卖陵。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡遭顶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泪蔫,到底是詐尸還是另有隱情棒旗,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布撩荣,位于F島的核電站铣揉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏餐曹。R本人自食惡果不足惜逛拱,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望台猴。 院中可真熱鬧朽合,春花似錦、人聲如沸饱狂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗡官。三九已至箭窜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衍腥,已是汗流浹背磺樱。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留婆咸,地道東北人竹捉。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像尚骄,于是被迫代替她去往敵國和親块差。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容