導語:YYModel庫是優秀的模型轉換庫,可自動處理模型轉換(從JSON到Model 和 Model到JSON)的工作。在項目中基于YYModel打造QSBaseModel,本文主要分析源碼,最后簡單介紹QSBaseModel的情況。
一、概述
YYModel庫可以實現 JSON轉Model、Model轉JSON、Model的序列化和反序列化等;在 JSON轉Model過程中,可以設置屬性名和JSON Key的映射、屬性的黑白名單等。YYModel的具體使用可以參考YYModel,那里已經說得很詳細了。
1、YYModel源碼文件#####
源碼文件總共5個,分別如下:
- YYModel.h :YYMode庫的頭文件,在項目中#import"YYModel.h",就可以使用YYModel了
- NSObject+YYModel.h 和 NSObject+YYModel.m:NSObject的YYModel分類,YYModel的主要功能代碼在這部分
- YYClassInfo.h 和 **YYClassInfo.m ** :包含YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo、YYClassInfo等類的定義和實現。將Model的成員變量、方法、成員屬性以及類等信息抽象成YYClassIvarInfo(成員變量)、YYClassMethodInfo(方法 )、YYClassPropertyInfo(成員屬性)、YYClassInfo(類)等對象。其中還定義了枚舉類型YYEncodingType,列舉了各類編碼信息,占據1個字節,包括值類型(YYEncodingTypeMask = 0xFF) 、方法限定類型(YYEncodingTypeQualifierMask = 0xFF00)、屬性修飾類型(YYEncodingTypePropertyMask = 0xFF0000 )。
2、JSON->Model基本流程#####
類方法 + (instancetype)yy_modelWithJSON:函數是解析JSON的入口。從它內部的函數調用鏈,可以發現解析流程主要分三步:
1)將JSON轉換成了字典
//NSObject (YYModel) - 解析JSON的入口函數
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
//....
}
//NSObject (YYModel) - 真正將JSON轉字典的函數
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
//...
return dic;
}
2)根據類對象,獲取YYModelMeta對象
YYModelMeta是描述Model的實例對象信息非常重要的類
//NSObject (YYModel) - 解析JSON的入口函數
+ (instancetype)yy_modelWithJSON:(id)json {
//....
return [self yy_modelWithDictionary:dic];
}
//NSObject (YYModel) - 獲得YYModelMeta對象
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
//...
//先獲取當前類對象,然后根據類對象獲得**YYModelMeta對象**
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
//...
}
說明2-1:modelCustomClassForDictionary:函數在獲得YYModelMeta對象之后,可以修改解析數據對應的Class,一般用來修改Model屬性值對應的Class。
// _YYModelMeta - 獲取和緩存了YYModelMeta對象
+ (instancetype)metaWithClass:(Class)cls {
//...
meta = [[_YYModelMeta alloc] initWithClass:cls];
//...
}
// _YYModelMeta - 真正將類對象 轉成 _YYModelMeta對象的函數
- (instancetype)initWithClass:(Class)cls {
//獲取類信息對象
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
//實例化_YYModelMeta其他成員變量....
}
說明2-2:YYModelMeta對象包含類信息YYClassInfo對象、解析數據等信息。而YYClassInfo對象中又包含方法列表、成員變量列表和屬性列表。這些信息都是JSON解析成Model所必須的。
3)解析字典,獲取Model
//NSObject (YYModel)
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
//...
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
說明3-1:modelCustomTransformFromDictionary:函數可以在JSON 轉換成Model 后調用,在該函數實現中,可以對數據進行校驗,校驗不通過,返回 NO,則該 Model 會被忽略;也可以處理YYModel自動轉換不能完成的工作。
//靜態函數
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
//...
}
說明3-2:ModelSetValueForProperty是真正處理數據解析,為屬性賦值的函數。
總結:YYModel中JSON解析的關鍵階段在于:獲取YYModelMeta對象 和 解析字典。為此,YYModel設計了YYModelMeta、_YYModelPropertyMeta、YYEncodingType、YYClassInfo、YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo等一系列類。
3、Model -> JSON的基本流程#####
實例方法- (NSString *)yy_modelToJSONObject是Model轉JSON的入口函數,從它內部的函數調用鏈,發現調用ModelToJSONObjectRecursive來將Model轉成id對象。最后只返回屬于NSArray或NSDictionary類型的id對象。
- (id)yy_modelToJSONObject {
id jsonObject = ModelToJSONObjectRecursive(self);
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
return nil;
}
說明1:Model -> JSON的關鍵在于:遞歸調用ModelToJSONObjectRecursive函數,實現對model各個屬性的的轉換,最終生成只包含NSArray/NSDictionary/NSString/NSNumber/NSNull的JSON對象。
說明2:通過yy_modelToJSONObject得到JSON對象之后,還可以通過NSJSONSerialization的dataWithJSONObject函數將JSONObject為NSData對象,這也解釋了為什么JSONObject必須是NSArray或NSDictionary類型,最后將NSData對象轉成NSString對象,即JSON字符串。
二、核心類之YYModelMeta
YYModelMeta通過- (instancetype)initWithClass:(Class)cls方法初始化YYModelMeta對象,傳入的是Model的Class。
1、YYModelMeta的成員變量
@interface _YYModelMeta : NSObject {
@package
//類信息對象
YYClassInfo *_classInfo;
//所有屬性的信息, key為鍵,propertyMeta為值
NSDictionary *_mapper;
// 所有屬性的信息
NSArray *_allPropertyMetas;
// 映射到keyPath屬性的信息
NSArray *_keyPathPropertyMetas;
//映射到一個數組的屬性信息
NSArray *_multiKeysPropertyMetas;
//所有屬性映射的個數
NSUInteger _keyMappedCount;
//model的類型
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary; //是否實現了modelCustomWillTransformFromDictionary函數
BOOL _hasCustomTransformFromDictionary; //是否實現了modelCustomTransformFromDictionary函數
BOOL _hasCustomTransformToDictionary; //是否實現了modelCustomTransformToDictionary函數
BOOL _hasCustomClassFromDictionary; //是否實現了modelCustomClassForDictionary函數
}
說明1-1:_mapper存放的是所有以key為鍵,_YYModelPropertyMeta對象為值的鍵值對;_allPropertyMetas、_keyPathPropertyMetas、_multiKeysPropertyMetas這些數組結構中存放的都是_YYModelPropertyMeta對象。
說明1-2:如果JSON key是使用keyPath來描述的,那么_keyPathPropertyMetas保存的是映射到keyPath屬性的信息。
說明1-3:如果一個屬性對應多個JSON key,那么_multiKeysPropertyMetas中保存的是映射到一個數組的屬性信息。
說明1-4:Model屬性的信息被封裝在YYModelPropertyMeta對象中。
2、YYModelMeta的初始化
// YYModelMeta - 根據Model的類對象獲取類信息對象classInfo,然后根據classInfo去初始化成員變量。
- (instancetype)initWithClass:(Class)cls初始化YYModelMeta{
//
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
if (!classInfo) return nil;
self = [super init];
//....
}
在該函數中,init過程如下:
1)根據Class獲取YYClassInfo實例
2)獲取黑名單(黑名單中的屬性被忽略,實現+ (NSArray *)modelPropertyBlacklist)
3)獲取白名單(白名單之外的屬性都被忽略,實現 + (NSArray *)modelPropertyWhitelist)
4)獲取類型為集合的屬性中存儲的類類型(實現+ (NSDictionary *)modelContainerPropertyGenericClass)
5)遍歷Model類及其父類(父類不包括NSObject)的屬性信息。在遍歷過程中將YYClassPropertyInfo對象轉成_YYModelPropertyMeta對象,根據2)、3)、4)獲取的信息,設置_allPropertyMetas屬性。
6)獲取屬性映射字典,如果沒有沒有指定映射或者說還有部分屬性沒有指定映射,就認為JSON中key(mappedToKey)和屬性名是一樣的,并據此設置_mapper、_keyPathPropertyMetas、_multiKeysPropertyMetas、_keyMappedCount屬性。
說明2-1:通過_YYModelMeta對象的初始化,可了解到我們為Model類設置的屬性映射、屬性容器類中數據類型、黑白名單等方法,是在這里發揮作用,影響Model各個屬性的賦值的。
三、核心類之YYClassInfo####
YYClassInfo描述Model類 類信息 的類
1、YYClassInfo的屬性信息#####
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
@property (nonatomic, strong, readonly) NSString *name; ///< class name
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
@end
說明1-1:YYModel將成員變量、方法、成員屬性以及類這四類信息,從C層面的函數調用抽象成OC的類,這些類分別是YYClassIvarInfo(成員變量)、YYClassMethodInfo(方法 )、YYClassPropertyInfo(成員屬性)、YYClassInfo(類)。
說明1-2:YYClassInfo中 成員變量信息ivarInfos、方法信息methodInfos、屬性信息propertyInfos分別存放三者的name為鍵,以后三者為值的字典,由于YYModel使用遍歷屬性的方式來達到模型轉換的目的,所以其中的propertyInfos起比較重要的作用。
2、YYClassInfo對象的初始化#####
在YYModelMeta的初始化中是調用classInfoWithClass:來獲取YYClassInfo對象的,然而真正初始化的地方在initWithClass函數
//YYClassInfo-獲得和緩存了YYClassInfo對象
+ (nullable instancetype)classInfoWithClass:(Class)cls{
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls];
//...
}
//...
return info;
}
//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;
}
//更新ivarInfos、propertyInfos、methodInfos等屬性值
- (void)_update{
//...
}
在該函數中,init過程如下:
1)根據Class設置_cls、_superCls、_isMeta等成員變量。
2)調用私有函數_update中。設置ivarInfos、propertyInfos、methodInfos等屬性(最重要)。
3)設置其他屬性。
說明2-1:_update函數中,將runtime得到的成員變量、屬性和方法信心 分別 封裝成YYClassIvarInfo對象、YYClassPropertyInfo對象和YYClassMethodInfo對象,然后以他們的名字為key,對象為值,存入對應的ivarInfos、propertyInfos、methodInfos這些字典中。
四、核心類之_YYModelPropertyMeta
在YYModelMeta的初始化中,很重要的工作是,將classInfo中的屬性信息propertyInfos中每一個YYClassPropertyInfo對象轉成_YYModelPropertyMeta對象。_YYModelPropertyMeta是對Model類屬性信息的描述類。
1、_YYModelPropertyMeta的成員變量
@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; ///< property's name
YYEncodingType _type; ///< property's type
YYEncodingNSType _nsType; ///< property's Foundation type
BOOL _isCNumber; ///< is c number type
Class _cls; ///< property's class, or nil
Class _genericCls; ///< container's generic class, or nil if threr's no generic class
SEL _getter; ///< getter, or nil if the instances cannot respond
SEL _setter; ///< setter, or nil if the instances cannot respond
BOOL _isKVCCompatible; ///< YES if it can access with key-value coding
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
/*
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; ///< the key mapped to
NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path)
NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
YYClassPropertyInfo *_info; ///< property's info
_YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end
說明1:_YYModelPropertyMeta的成員變量中包括屬性類型_type、_nsType、屬性Class、集合類中的Class(_genericCls)、setter和getter方法等。
說明2:_mappedToKey是映射到的key(@{property : key});_mappedToKeyPath映射到的keyPath(@{property : key1.key2})
_mappedToKeyArray映射到的數組(@{property : @[key1, key2]})。而每個_YYModelPropertyMeta中,這三者只有其中一個會有值。有了這三個屬性,就可以獲取需要轉化的對應字典的value了。
2、_YYModelPropertyMeta的初始化函數
//_YYModelPropertyMeta
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
//...
return meta;
}
說明2-1:在該函數中,根據propertyInfo對象和generic信息初始化_YYModelPropertyMeta中各個成員變量。其中重要是屬性Foundation類型_nsType的賦值。由于YYModel將屬性的類型大致分為三類:C數值類型、NS系統自帶基本類類型 以及 非常規類型(如CGSize等結構體),框架中提供一些函數判斷類型,方便后面的屬性賦值處理。
1)如果屬性類型是NS系統類型,更新_nsType屬性值,具體的類型通過調用YYClassGetNSType來判斷。源碼如下:
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
if (!cls) return YYEncodingTypeNSUnknown;
if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
return YYEncodingTypeNSUnknown;
}
說明2-2:由于類簇的原因,我們是無法在runtime時獲取屬性是否是mutable的,所以需要先判斷是否為mutable。
**2) **調用YYEncodingTypeIsCNumber判斷屬性的類型是否是C數值類型
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
switch (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 YYEncodingTypeLongDouble: return YES;
default: return NO;
}
}
說明2-3:將BOOL值結果賦值給_isCNumber,方便后面屬性賦值時候的處理。
五、Dictionary -> JSON (解析關鍵)####
在解析之前,獲取了YYModelMeta對象,調用yy_modelSetWithDictionary去處理解析。
1、從yy_modelSetWithDictionary看解析的過程#####
- (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);
context.dictionary = (__bridge void *)(dic);
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
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);
}
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
該函數處理流程:
1)如果實現modelCustomWillTransformFromDictionary:這個函數,就按照實現方法修改解析的Dic的數據;
2)創建了一個模型集合上下文context,存放了modelMeta對象、model對象、解析的數據字典dictionary等數據,將context交給ModelSetWithPropertyMetaArrayFunction函數去解析(給model賦值);
3)解析完成后,可以實現modelCustomTransformFromDictionary方法,對model的屬性進行更改。
說明1-1: 由于modelCustomWillTransformFromDictionary和modelCustomTransformFromDictionary函數都是交給開發去自定義實現的,不是框架處理的事情,我們關注源碼中是如何解析字典的。
說明1-2:當modelMeta->_keyMappedCount大于等于CFDictionaryGetCount((CFDictionaryRef)dic)的時候,遍歷字典,設置并以字典為基準,設置模型中與字典相對應的屬性(ModelSetWithDictionaryFunction函數),如果_keyPathPropertyMetas不為空,設置映射到keyPath的屬性,如果_multiKeysPropertyMetas不為空,設置映射到數組的屬性。否則直接通過_allPropertyMetas設置所有屬性。
2、ModelSetWithDictionaryFunction函數功能#####
在yy_modelSetWithDictionary函數中,只有當modelMeta->_keyMappedCount大于等于CFDictionaryGetCount((CFDictionaryRef)dic)時,才調用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)];
__unsafe_unretained id model = (__bridge id)(context->model);
while (propertyMeta) {
// 映射到同個key之后,這里循環賦給屬性相同的值
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
該函數處理流程:
1)根據字典的key從_mapper中獲取對應的_YYModelPropertyMeta
2) 調用ModelSetValueForProperty設置屬性值。
3)如果propertyMeta的_next不為空,即表示有多個屬性被映射到了同一個key。這樣只需要從字典中取一次value,就可以設置被映射到同一個key的所有屬性。
3、ModelSetWithPropertyMetaArrayFunction函數功能#####
通過_allPropertyMetas設置時,則需要對每個屬性都對字典做一次取值操作
//數組回調函數
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) {
// 映射到多個key
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
// 映射到keyPath
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
// 映射到一個key
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}
if (value) {
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}
該函數處理流程:
1)如果一個屬性映射到多個JSON key(propertyMeta->_mappedToKeyArray不為空),那么只取第一個匹配成功的key,后續的key將會被略過。
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
id value = nil;
for (NSString *key in multiKeys) {
if ([key isKindOfClass:[NSString class]]) {
value = dic[key];
if (value) break;
} else {
value = YYValueForKeyPath(dic, (NSArray *)key);
if (value) break;
}
}
return value;
}
-
如果映射關系中,通過key1.key2來描述JSON Key(propertyMeta->_mappedToKeyPath不為空),那么將映射的keyPath以.為分隔符拆分成多個字符串,并以數組的形式存儲,最終用循環獲取value的方式代替valueForKeyPath:避免從非字典取value時發生崩潰。
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
id value = nil;
for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
value = dic[keyPaths[i]];
if (i + 1 < max) {
if ([value isKindOfClass:[NSDictionary class]]) {
dic = value;
} else {
return nil;
}
}
}
return value;
}
3)獲取value的值,然后交給ModelSetValueForProperty去設置對應屬性的值。
4、真正設置屬性值ModelSetValueForProperty函數#####
在ModelSetValueForProperty中,為屬性賦值分為三步:
1) 處理屬性類型是C的數值類型的情況
if (meta->_isCNumber) {
NSNumber *num = YYNSNumberCreateFromID(value);
ModelSetNumberToProperty(model, num, meta);
if (num) [num class]; // hold the number
}else{
//...
}
2) 處理屬性類型是NS系統定義類型的情況
如果是屬性類型是如NSString、NSNumber這樣的非集合類、采用objc_msgSend直接調用setter,給屬性賦值;如果是集合類,處理比較麻煩,以屬性類型是NSArray或NSMutableArray(_nsType是YYEncodingTypeNSArray或YYEncodingTypeNSMutableArray)的處理源碼為例:
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];
}
}
((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);
}
}
}
1)在沒有指定集合中的數據類型,即_genericCls為nil的情形下,如果value是NSArray或者NSSet類型,那么YYModel將value
直接賦給屬性,不做任何解析。
2)在指定了集合中的數據類型,即_genericCls不為nil的情形下,會對每個元素進行解析并構造成相應的實例。如果集合元素依然是一個字典,那么就會調用yy_modelSetWithDictionary解析。在解析的過程中,可以實現modelCustomClassForDictionary:方法,重新指定集合中的數據類型。
3) 處理屬性類型是非常規類型
非常規類型是C數值類型、NS定義的類型之外的類型,包括但不限于自定義Model類、CG結構體等。主要是自定義Model類,以解析自定義Model類為例,源碼如下:
case YYEncodingTypeObject: {
if (isNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
NSObject *one = nil;
if (meta->_getter) {
one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
}
if (one) {
[one yy_modelSetWithDictionary:value];
} else {
Class cls = meta->_cls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
}
}
}
說明:如果屬性是自定義Model類,通過Class實例化對象,然后調用yy_modelSetWithDictionary解析數據,給Model實例各個屬性賦值。
六、Model -> JSONModel (重點)####
分析ModelToJSONObjectRecursive函數,可以看出根據Model的類型來處理的,Model的類型分:非集合類型、集合類型、自定義Model類
1、model是非集合類型的處理#####
//model是基本類型 : kCFNull、NSString、NSNumber或者nil,直接返回model
if (!model || model == (id)kCFNull) return model;
if (!model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;
//model類型是NSURL、NSAttributedString、NSDate類型,轉成字符串返回。是NSData就返回nil
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
if ([model isKindOfClass:[NSData class]]) return nil;
2、model是集合類型的處理#####
//如果是NSDictionary,能JSON化就直接返回,否則調用ModelToJSONObjectRecursive遞歸處理,最后添加到字典中
if ([model isKindOfClass:[NSDictionary class]]) {
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、NSArray,并且元素是基本類型就直接添加到數組中,否則調用ModelToJSONObjectRecursive嵌套解析成基本類型,然后添加到數組中
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;
}
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;
}
3、model是自定義Model類的處理#####
獲取自定義Model類的modelMeta信息,然后遍歷modelMeta._mapper,通過映射關系獲取屬性值,然后構造成字典。
1) 根據propertyMet對象提供的屬性類型相關信息,處理屬性值value,如果value為nil,放棄當前屬性值的處理,去處理下一個屬性值。
if (!propertyMeta->_getter) return;
id value = nil;
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 {
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
if (!value) return;
2)根據propertyMeta的_mappedToKeyPath或_mappedToKey構造字典
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
if (i + 1 == max) { // end { ext = { d = Apple; }; }, 最后的key才賦值, 即superDic[@"d"] = @"Apple"
if (!superDic[key]) superDic[key] = value;
break;
}
subDic = superDic[key];
if (subDic) {
// 說明這一層字典已經有鍵值對了
if ([subDic isKindOfClass:[NSDictionary class]]) {
// 拷貝成可變的(沒這一句也可,因為剛開始時創建的都是NSMutableDictionary), 方便i + 1 == max時進行賦值
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break;
}
} else {
// key下沒有value,創建可變字典賦給當前的key
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
// 最頂層的字典(@{@"a" : @{@"b" : @"c"}},即字典@{@"b" : @"c"})
superDic = subDic;
subDic = nil;
}
} else {
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
七、QSBaseModel封裝
1、為什么要封裝YYModel
1)YYModel很強大,可以通過實現NSObject+YYModel中的相關方法操作,干預模型轉換的過程。由于項目中后臺提供的數據接口格式固定,JSON解析中最大的需要是可以指定屬性名和JSON Key的映射 以及 指定容器類中的數據類型,為了稍稍限制開發人員自由,讓大家一起遵循統一的規范。
2)通過YYModel的相關方法重寫Model類的Coding/Copying/hash/equal/description方法,只要Model類繼承QSBaseModel就可以很方便地使用這些方法。
3)如果項目后期替換模型轉換庫,對業務也不會有什么影響。
2、QSBaseModel的定義和實現#####
//QSBaseModel.h
@interface QSBaseModel : NSObject<NSCoding,NSCopying>
+ (instancetype)modelFromJSON:(id)json;
- (NSString *)modelToJSONString;
@end
//QSBaseModel.m
#import "YYModel.h"
#import "QSBaseModel.h"
@implementation QSBaseModel
#pragma mark - 模型轉換(public)
+ (instancetype)modelFromJSON:(id)json{
return [self yy_modelWithJSON:json];
}
- (NSString *)modelToJSONString {
return [self yy_modelToJSONString];
}
#pragma mark - 序列化和反序列化
- (void)encodeWithCoder:(NSCoder *)aCoder{
[self yy_modelEncodeWithCoder:aCoder];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
return [self yy_modelInitWithCoder:aDecoder];
}
#pragma mark - 實現copy方法(實現深拷貝)
- (id)copyWithZone:(NSZone *)zone {
return [self yy_modelCopy];
}
#pragma mark -重寫hash、isEqual:和description方法
- (NSUInteger)hash {
return [self yy_modelHash];
}
- (BOOL)isEqual:(id)object {
return [self yy_modelIsEqual:object];
}
- (NSString *)description{
return [self yy_modelDescription];
}
@end
說明1:在指定屬性名和JSON Key的映射時,原則上不使用keyPath描述JSON Key。
說明2:因為YYModel中將JSON轉成Model的過程是,先生成Model實例,再用JSON數據給Model的屬性賦值,所以會出現Model中部分屬性值是nil的情況,使用這些Model的屬性值前需要簡單校驗。主要是對屬性類型是字符串、數組、字典、集合的校驗。下面是校驗的宏定義。
#define ISVALID_STRING(x) (x && [x isKindOfClass:[NSString class]] && [x length])
#define ISVALID_ARRAY(x) (x && [x isKindOfClass:[NSArray class]] && [x count])
#define ISVALID_DICTIONARY(x) (x && [x isKindOfClass:[NSDictionary class]] && [x count])
#define ISVALID_SET(x) (x && [x isKindOfClass:[NSSet class]] && [x count])
結束####
參考資料:
YYModel
YYModel閱讀小記
其他:
這里只是簡單記錄了YYModel模型轉換的過程,YYModel有很多值得學習的地方,后面再談