概述
? iOS源碼解析—YYModel(YYClassInfo)分析了如何根據OC的Class對象構建YYClassInfo對象,為接下來的JSON數據和Model轉換作準備,這篇文章開始講解NSObject+YYModel。
NSObject+YYModel.h
? 分析NSObject+YYModel.h文件,包括3個Category和一個protocol,分別是:
NSObject(YYModel)
NSArray(YYModel)
NSDictionary(YYModel)
YYModel <NSObject>
-
NSObject(YYModel)
負責擴展NSObject類,提供了Model相關方法,下面是代碼注釋:
@interface NSObject (YYModel) //根據JSON對象創建Model + (nullable instancetype)yy_modelWithJSON:(id)json; //根據NSDictionary創建Model + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary; //將JSON對象的各字段映射到Model的各個字段 - (BOOL)yy_modelSetWithJSON:(id)json; //將NSDictionary的各字段映射到Model的各個字段 - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic; //根據Model創建JSON對象 - (nullable id)yy_modelToJSONObject; //根據Model創建JSON數據流 - (nullable NSData *)yy_modelToJSONData; //根據Model創建JSON字符串 - (nullable NSString *)yy_modelToJSONString; //復制Model - (nullable id)yy_modelCopy; //對Model進行歸檔 - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder; //對Model進行解檔 - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder; //model的hash值 - (NSUInteger)yy_modelHash; //判斷Model和參數model是否相同 - (BOOL)yy_modelIsEqual:(id)model; //輸出Model的相關信息 - (NSString *)yy_modelDescription; @end
例如定義一個Student類,代碼如下:
@interface College : NSObject @property (nonatomic, copy) NSString *name; @end //Student @interface Student : NSObject <YYModel> @property (nonatomic, copy) NSString *name; //名字 @property (nonatomic, assign) NSInteger age; //年齡 @property (nonatomic, strong) College *college; //學校 @end
同時創建一個字典對象,如下:
NSDictionary *studentDic = @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}};
調用[Student yy_modelWithDictionary:studentDic]方法,創建一個Student類型的Model對象,然后將studentDic的每個value賦值給Model相應屬性。property的名稱和key一一對應。在轉化的過程中,可以實現Model嵌套的情況,如Student中嵌套一個College對象。
調用[Student yy_modelToJSONObject]方法,創建一個字典對象,各鍵值對中的key是Model相應屬性的名稱,value是屬性值。在轉化的過程中,也支持嵌套的情況。
-
NSArray(YYModel)
負責擴展NSArray類,下面是代碼注釋:
@interface NSArray (YYModel) //根據JSON創建Model數組(JSON是數組格式) + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json; @end
例如有一個數組,如下:
NSArray *studentArr = @[@{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}}, @{@"name" : @"Alex", @"age" : @19, @"college" : @{@"name" : @"Harvard"}}, @{@"name" : @"Sunny", @"age" : @20, @"college" : @{@"name" : @"Yale"}}];
調用[NSArray yy_modelArrayWithClass:Student json:studentArr]方法,創建一個Model數組,數組中的每個元素是Student對象。
-
NSDictionary(YYModel)
負責擴展NSDictionary類,下面是代碼注釋:
@interface NSDictionary (YYModel) //根據JSON的value和Class對象創建Model,并創建一個新字典,取json的key作為key,取Model作為value + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; @end
例如創建一個字典,如下:
NSDictionary *playerDic = @{@"player1" : @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}}, @"player2" : @{@"name" : @"Alex", @"age" : @19, @"college" : @{@"name" : @"Yale"}}};
調用[NSDictionary yy_modelDictionaryWithClass:Student json:playerDic]得到一個新的字典studentDic,如下:
字典studentDic中的key對應原字典playerDic中的key,studentDic中的Model由playerDic中的value轉化得到。
-
@protocol YYModel
YYModel是iOS協議,里面聲明了一些方法,調用類通過實現這些方法,實現JSON和Model之間轉換過程中的特殊處理。
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper; + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass; + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary; + (nullable NSArray<NSString *> *)modelPropertyBlacklist; + (nullable NSArray<NSString *> *)modelPropertyWhitelist; - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic; - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic; - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
下面講一下協議中的每個方法:
-
modelCustomPropertyMapper方法,可以指定json和model轉化過程中key的映射關系,如果存在以下的字典:
NSDictionary *studentDic = @{@"NAME" : @"Tomy", @"AGE" : @{@"num":@18}, @"college" : @{@"name" : @"NJU"}};
如果需要將studentDic轉化成Student類型的model,需要實現如下modelCustomPropertyMapper方法:
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper { return @{@"name" : @"NAME", @"age" : @"AGE.num"}; }
返回的鍵值對中的key是Student的property名稱,value是studentDic中的key。
-
如果Student中存在property是對象數組或者字典,實現modelContainerPropertyGenericClass方法,例如Student存在屬性mobilePhones,維護一組MobilePhone對象
@interface MobilePhone : NSObject @property (nonatomic, copy) NSString *brand; @property (nonatomic, assign) NSInteger phoneNumber; @end @interface Student : NSObject <YYModel> @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) College *college; @property (nonatomic, strong) NSArray *mobilePhones; //MobilePhone對象數組 @end NSDictionary *studentDic = @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}, @"mobilePhones" : @[@{@"brand" : @"iphone",@"phoneNumber" : @123456}, @{@"brand" : @"HUAWEI",@"phoneNumber" : @123456}]};
調用[Student yy_modelWithDictionary:studentDic]方法將studentDic中的mobilePhones轉化成Student的mobilePhones屬性,需要實現如下:
+ (nullable NSDictionary<NSString*, id>*)modelContainerPropertyGenericClass { return @{@"mobilePhones" : [MobilePhone class]}; }
-
modelCustomClassForDictionary方法用于指定生成Model的類型,如果沒有實現該方法,用默認的類型,例如Student實現modelCustomClassForDictionary,如下:
+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary { if ([dictionary[@"name"] isEqualToString @"Graduated"]) { return [Graduated class]; } else { return [self class]; } }
調用[Student yy_modelWithDictionary:studentDic]方法,如果studentDic[@"name"]等于@"Graduated",則不創建Student類型的對象,而是創建Graduated類型的對象。
-
modelPropertyBlacklist方法和modelPropertyWhitelist方法的作用相反,如果實現了modelPropertyBlacklist,該方法返回一個數組,數組中的值指定了Model中不需要被賦值的property,例如代碼如下:
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist { return @[@"age", @"college"]; }
則Student中的age和college屬性不會被賦值。如果實現了modelPropertyWhitelist方法,該方法返回一個數組,數組中的值指定了Model中僅需要被賦值的property,例如代碼如下:
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist { return @[@"age", @"college"]; }
則Student中只有age和college屬性會被賦值。
modelCustomWillTransformFromDictionary:方法作用于根據JSON創建Model對象之前,該方法可以把JSON字典轉化成一個新的字典。
modelCustomTransformFromDictionary:方法作用于根據JSON創建Model對象之后,該方法可以對生成的Model做一些處理。
modelCustomTransformToDictionary:方法作用于根據Model對象創建JSON之后,該方法可以對生成的JSON字典做一些處理。
-
NSObject+YYModel.m
首先定義了兩個類,_YYModelMeta和_YYModelPropertyMeta,分別封裝了Model的信息和Model中各屬性的信息。
_YYModelMeta維護了Class的相關信息,下面是注釋:
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo; //關聯的YYClassInfo對象
NSDictionary *_mapper; //維護一個鍵值對,key是屬性名,value是_YYModelPropertyMeta對象
//維護一個數組,里面的元素是_YYModelPropertyMeta對象
NSArray *_allPropertyMetas;
//維護一個數組,里面的元素是_YYModelPropertyMeta對象
NSArray *_keyPathPropertyMetas;
//維護一個數組,里面的元素是_YYModelPropertyMeta對象
NSArray *_multiKeysPropertyMetas;
//通過映射相關
NSUInteger _keyMappedCount;
//對象類型
YYEncodingNSType _nsType;
...
}
@end
_YYModelMeta是通過YYClassInfo對象的信息構建得到的。首先調用metaWithClass:cls方法,該方法如下:
+ (instancetype)metaWithClass:(Class)cls {
...
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); //從緩存中取
...
if (!meta || meta->_classInfo.needUpdate) {
meta = [[_YYModelMeta alloc] initWithClass:cls]; //創建一個新的_YYModelMeta對象
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
}
}
return meta;
}
維護了一個鍵值對cache作為緩存,用cls作為key,調用CFDictionaryGetValue方法去緩存中查找,如果有,直接返回構建好的_____YYModelMeta對象,如果沒有找到或者needUpdate屬性標記為true,則根據cls創建一個新的_YYModelMeta對象,并且存入緩存。這樣不需要每次都創建,提高了性能。
接下來看一下initWithClass:方法,
創建一個YYClassInfo對象,在上一篇文章中分析了YYClassInfo的創建方法;
判斷調用層是否實現modelPropertyBlacklist方法,如果實現了,返回一組黑名單,數組中包含的屬性名不會被轉化。
判斷調用層是否實現modelPropertyWhitelist方法,如果實現了,返回一組白名單,不在數組中的屬性名不會被轉化。
如果調用層實現了modelContainerPropertyGenericClass方法,則維護一個字典genericMapper,字典的value如果是NSString類型,需要轉成Class類型。
-
遍歷對象的屬性,生成_YYModelPropertyMeta,代碼注釋如下:
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; 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; //根據classInfo和propertyInfo創建_YYModelPropertyMeta _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; //存入allPropertyMetas字典中 } curClassInfo = curClassInfo.superClassInfo; } if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; //賦值給_allPropertyMetas屬性
根據classInfo信息和propertyInfo信息創建_YYModelPropertyMeta對象,并用_allPropertyMetas字典存儲,字典中的key是屬性名,value是_YYModelPropertyMeta對象。
-
如果調用層實現了modelCustomPropertyMapper方法,說明存在JSON字典key和屬性名之間的映射關系,需要遍歷這些屬性,做特殊處理,如下:
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { //這些屬性名對應的_YYModelPropertyMeta從allPropertyMetas字典中刪除 _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; [allPropertyMetas removeObjectForKey:propertyName]; //mappedToKey作為新的key,存入mapper,value是propertyMeta,同時對mappedToKey做一些分割處理,表示映射的路徑 if ([mappedToKey isKindOfClass:[NSString class]]) { ... if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } ... }]; }
-
維護一個_mapper字典,key是mappedToKey,對于customMapper字典中不存在的屬性,mappedToKey是其本身的屬性名,value是創建的propertyMeta。同時維護一個_keyPathPropertyMetas數組和_multiKeysPropertyMetas數組,專門存儲customMapper中存在的,即存在映射關系的_YYModelPropertyMeta。
其中YYModelPropertyMeta類維護了property的相關信息,下面是相關注釋:
@interface _YYModelPropertyMeta : NSObject { @package NSString *_name; //屬性名 YYEncodingType _type; //屬性類型 YYEncodingNSType _nsType; //屬性如果是NSObject類型的對象,相應的類型 BOOL _isCNumber; //是否是數字類型 Class _cls; //屬性的類 Class _genericCls; // SEL _getter; //getter方法 SEL _setter; //setter方法 BOOL _isKVCCompatible; //是否可以處理KVC BOOL _isStructAvailableForKeyedArchiver; //是否可以archiver/unarchiver BOOL _hasCustomClassFromDictionary; // NSString *_mappedToKey; //該屬性名對應的JSON字典中的key NSArray *_mappedToKeyPath; //該屬性名對應的JSON字典中的keyPath,keyPath是一個路徑,例如AGE.NUM,該數組存儲@[AGE,NUM] NSArray *_mappedToKeyArray; //該屬性名對應的JSON字典中的key,如果@[@"age",@"num"] YYClassPropertyInfo *_info; //關聯的YYClassPropertyInfo _YYModelPropertyMeta *_next; //另一個YYModelPropertyMeta對象,key名稱相同 } @end
_YYModelPropertyMeta對象是通過YYClassPropertyInfo對象的信息構建得到的。
主要方法
下面分析幾個重要的方法:
-
yy_modelWithJSON:方法
該方法首先調用_yy_dictionaryWithJSON:方法將JSON數據進行轉化,如果JSON數據是NSString或者NSData格式,轉化為字典對象,否則直接返回。然后調用yy_modelWithDictionary:方法,代碼如下:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { ... Class cls = [self class]; //1.metaWithClass _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } ... //2.根據json字典對新創建的model進行賦值 NSObject *one = [cls new]; if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; }
該方法主要分為2個步驟:
根據Class對象cls創建_YYModelMeta
-
調用yy_modelSetWithDictionary:對model進行賦值,主要代碼注釋如下:
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { //1、遍歷dic中的key和value,調用ModelSetWithDictionaryFunction方法進行屬性賦值 CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); //2、如果_keyPathPropertyMetas存放有映射關系,調用ModelSetWithPropertyMetaArrayFunction方法進行賦值 if (modelMeta->_keyPathPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } //3、如果_multiKeysPropertyMetas存放有映射關系,調用ModelSetWithPropertyMetaArrayFunction方法進行賦值 if (modelMeta->_multiKeysPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); }
主要分為2種情況:
-
如果Model中不存在屬性名的映射,調用CFDictionaryApplyFunction方法遍歷JSON字典中的每一個鍵值對,調用ModelSetWithDictionaryFunction方法對每一個鍵值對進行處理,由于_YYModelMeta內部維護了一個mapper字典,通過mapper取出YYModelPropertyMeta對象,最終調用ModelSetValueForProperty方法進行該屬性的賦值。方法如下:
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); //根據key在mapper中查找對應的propertyMeta,如果沒有,不進行后續操作 __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; }; }
-
如果Model中存在_keyPathPropertyMetas屬性或者_multiKeysPropertyMetas屬性,說明_keyPathPropertyMetas數組或者_multiKeysPropertyMetas數組中維護了一組Model中的屬性,這些屬性的名稱存在映射關系,這種情況下調用CFArrayApplyFunction方法對keyPathPropertyMetas數組或者multiKeysPropertyMetas數組進行遍歷,并調用ModelSetWithPropertyMetaArrayFunction方法對每個屬性元素進行處理,該方法最終也會調用ModelSetValueForProperty方法進行屬性賦值。代碼注釋如下:
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { ... //根據映射關系找到JSON中的value 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); } }
上述兩種情況最終會調用ModelSetValueForProperty方法在賦值的時候做了類型兼容,下文中具體分析。
-
-
yy_modelToJSONObject方法
該方法調用ModelToJSONObjectRecursive方法將Model對象轉化成一個字典或者數組,需要轉化的Model對象可以分為3類:
第一類是一些基本類型,直接返回或者做一些處理返回,代碼如下:
if ([model isKindOfClass:[NSString class]]) return model; if ([model isKindOfClass:[NSNumber class]]) return model; if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString; if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
第二類是NSDictionary、NSSet、NSArray等類型,它們不屬于自定義的對象類型,代碼如下:
if ([model isKindOfClass:[NSDictionary class]]) { //如果是NSDictionary類型,遍歷字典,用遞歸的方式賦值給新的字典,并返回新的字典。 if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableDictionary *newDic = [NSMutableDictionary new]; [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description; if (!stringKey) return; id jsonObj = ModelToJSONObjectRecursive(obj); if (!jsonObj) jsonObj = (id)kCFNull; newDic[stringKey] = jsonObj; }]; return newDic; } //如果是NSSet類型,遍歷NSSet,用遞歸的方式賦值給新的set,并返回新的set。 if ([model isKindOfClass:[NSSet class]]) { NSArray *array = ((NSSet *)model).allObjects; if ([NSJSONSerialization isValidJSONObject:array]) return array; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in array) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } //如果是NSArray類型,遍歷NSArray,用遞歸的方式賦值給新的array,并返回新的array。 if ([model isKindOfClass:[NSArray class]]) { if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in (NSArray *)model) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } ...
第三種是自定義的Model對象類型,首先調用metaWithClass獲取自定義Model的_YYModelMeta對象,通過上文知道_YYModelMeta內部維護了一個mapper字典,用于維護Model內部的屬性類型信息,然后遍歷mapper中的每一個元素(_YYModelPropertyMeta),根據元素的類型,進行相應的處理,如下:
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { if (propertyMeta->_isCNumber) { //數值類型 value = ModelCreateNumberFromProperty(model, propertyMeta); } else if (propertyMeta->_nsType) { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); //遞歸調用 } else { ... } }]
如果_YYModelPropertyMeta存在mappedToKeyPath屬性,需要將屬性名映射到指定的key上面。
-
yy_modelArrayWithClass:json:方法
該方法通過JSON數組和Class創建Model數組,首先調用yy_modelArrayWithClass: array:方法,然后在該方法中遍歷數組,數組中每個元素是字典類型,調用yy_modelWithDictionary:方法進行轉化,然后將轉化后的Model添加進新數組中,最后返回這個新數組。代碼如下:
+ (NSArray *)yy_modelArrayWithClass:(Class)cls array:(NSArray *)arr { if (!cls || !arr) return nil; NSMutableArray *result = [NSMutableArray new]; for (NSDictionary *dic in arr) { //遍歷數組 if (![dic isKindOfClass:[NSDictionary class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic]; //dic->Model if (obj) [result addObject:obj]; //添加Model } return result; }
-
yy_modelDictionaryWithClass:json:方法
該方法和上面的方法3類似,首先調用yy_modelDictionaryWithClass:dictionary:方法,然后在該方法中遍歷JSON字典,字典中的每個元素同樣是字典,調用yy_modelWithDictionary:方法進行轉化,然后將轉化后的Model加入新的字典中,key是之前JSON字典中的key。代碼如下:
+ (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic { if (!cls || !dic) return nil; NSMutableDictionary *result = [NSMutableDictionary new]; for (NSString *key in dic.allKeys) { //遍歷字典 if (![key isKindOfClass:[NSString class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic[key]]; //dic[key]->Model if (obj) result[key] = obj; //添加Model } return result; }
類型轉化和兼容
YYModel的總體思想是以Model屬性的類型為準,如果JSON中對應名稱的value的類型和Model屬性類型不一致,會對value的類型進行轉化,保證和Model屬性的類型一致。如果兼容不了,不進行屬性賦值。下面分析一下ModelSetValueForProperty方法:
該方法上文中提到該方法是用來JSON轉成Model的過程中對Model中的屬性進行賦值的方法,該方法做了部分基本類型的兼容:
-
如果Model的屬性是數值類型,與之對應的JSON的value是其它類型,會進行轉化,如下:
if (meta->_isCNumber) { NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num) [num class]; // hold the number }
YYNSNumberCreateFromID會將value轉化成NSNumber類型,有可能返回nil。
-
如果Model的屬性是字符串類型,會判斷value的類型,進一步處理:
case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { ... } else if ([value isKindOfClass:[NSNumber class]]) { ... //轉化成NSString } else if ([value isKindOfClass:[NSData class]]) { ... //轉化成NSString } else if ([value isKindOfClass:[NSURL class]]) { ... //轉化成NSString } else if ([value isKindOfClass:[NSAttributedString class]]) { ... //轉化成NSString } break;
根據value類型的不同,調用不得方法轉成字符串類型。
如果Model的屬性是NSNumber類型,需要將value轉化成NSNumber類型。
如果Model的屬性是NSData、NSDate、NSURL類型等,都需要value先轉成這些類型。
-
不是所有的類型都可以轉化,如果屬性的對象是數組、集合或者字典類型,但是value不是對應的類型,不會轉化,直接返回,不進行后續的屬性賦值。例如字典類型,代碼如下:
case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: { if ([value isKindOfClass:[NSDictionary class]]) { //value為字典類型是,進行處理,否則不處理 ... } } break;
其它方法
YYModel還提供了一些工具方法,下面簡單分析一下:
-
ModelDescription方法,用來描述該Model對象的相關信息,一般用于log輸出。該方法主要是根據Model對象的類型,做不同的處理:
如果是一些簡單類型,做一些處理后直接輸出,下面是代碼注釋:
case YYEncodingTypeNSNumber: case YYEncodingTypeNSDecimalNumber: case YYEncodingTypeNSDate: case YYEncodingTypeNSURL: { return [NSString stringWithFormat:@"%@",model]; }
如果是集合、數組或者字典類型,需要遍歷其中每個元素,遞歸調用ModelDescription,生成每個元素的輸出信息,拼接成一個字符串輸出,例如是NSArray:
case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: { NSArray *array = (id)model; NSMutableString *desc = [NSMutableString new]; if (array.count == 0) { return [desc stringByAppendingString:@"[]"]; } else { [desc appendFormat:@"[\n"]; for (NSUInteger i = 0, max = array.count; i < max; i++) { NSObject *obj = array[i]; [desc appendString:@" "]; //生成每個元素的輸出信息,拼接字符串 [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)]; [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; } [desc appendString:@"]"]; return desc; } }
如果是自定義Model對象,則遍歷對象中每個屬性,獲取與之對應的_YYModelPropertyMeta對象,進一步判斷_YYModelPropertyMeta對象的類型,做不同的處理。如果屬于NSObject類型,遞歸調用ModelDescription方法。代碼如下:
for (NSUInteger i = 0, max = properties.count; i < max; i++) { _YYModelPropertyMeta *property = properties[i]; NSString *propertyDesc; if (property->_isCNumber) { NSNumber *num = ModelCreateNumberFromProperty(model, property); propertyDesc = num.stringValue; } else { switch (property->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = ModelDescription(v); if (!propertyDesc) propertyDesc = @"<nil>"; } break; } }
-
YYNSDateFromString方法,用來將字符串類型轉化成NSDate類型。因為NSDate是OC的對象,而JSON數據格式中不存在日期時間類型,因此需要進行轉化。通常是將具有固定格式的字符串轉成NSDate類型,例如:"yyyy-MM-dd HH:mm:ss"。
該方法的主要思路是創建不同類型的NSDateFormatter對象和一組block,然后根據傳入的字符串參數的格式,執行相應的block,在block中再根據字符串參數調用相應的dateFormatter進行轉化。代碼注釋如下:
dispatch_once(&onceToken, ^{ { NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init]; formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss"; ... blocks[19] = ^(NSString *string) { if ([string characterAtIndex:10] == 'T') { return [formatter1 dateFromString:string]; } else { return [formatter2 dateFromString:string]; } }; ... YYNSDateParseBlock parser = blocks[string.length]; //根據string選擇相應的block if (!parser) return nil; return parser(string); //執行block,選取dateFormatter進行轉化,輸出NSDate對象 }
結尾
YYModel作為一個負責JSON數據和Model轉化的庫,十分易用和高效,特別是做了一些類型的兼容和轉化,避免了服務端接口數據類型和客戶端Model對象類型不兼容導致的問題,例如執行了不存在的方法而導致崩潰。另一方面,對YYModel的學習在一定程度也促進了對runtime機制的學習和了解。
關于YYModel的分析到這兒先告一段落,由于本人的iOS基礎有待提升,再加上表達能力有限,文中許多地方的分析和思路,表達的不是很準確和清楚,希望通過今后的學習和練習,提升自己的水平。