上一篇中《從YYModel源碼中可以學到什么:后篇》中主要學習了YYModel
的源碼結構,只是分享了YYModel
整體結構。
承接上篇,本文將解讀YYModel
如何進行JSON模型轉換的,接下來一起揭開YYModel
的神秘面紗吧!
首先來看JSON是如何轉為Model。查看YYModel
的接口,提供了一個方法:
+ (instancetype)yy_modelWithJSON:(id)json;
注意json
為id
類型,接收三種不同類型參數NSString
,NSData
,NSDictionay
。下面是內部實現:
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
return [self yy_modelWithDictionary:dic];
}
方法中調用了一個私有方法_yy_dictionaryWithJSON:
,該方法將id
類型的json
(NSDictionary, NSString, NSData)轉為字典。
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
// 驗證json
if (!json || json == (id)kCFNull) return nil;
// 兩個臨時變量
NSDictionary *dic = nil;
NSData *jsonData = nil;
// 根據json類型,進行相應處理
if ([json isKindOfClass:[NSDictionary class]]) {
dic = json;
} else if ([json isKindOfClass:[NSString class]]) {
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
} else if ([json isKindOfClass:[NSData class]]) {
jsonData = json;
}
// 使用NSJSONSerialization將Data轉為字典
if (jsonData) {
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
return dic;
}
該方法很簡單,就是判斷
id
類型是指(NSDictionary,NSString,NSData)中的哪一個,分別處理。在
json == (id)kCFNull
中kCFNull
是什么意思?這條語句起到什么作用?
nil
: 指向OC中對象的空指針
Nil
: 指向OC中類的空指針
NULL
:定義其他類型(基本類型、C類型)的空指針
NSNull
:集合對象中,表示空值的對象。如給數組設置空值,使用NSNull
,而不能使用nil
。
kCFNull
是NSNull
的單例。
if (!json || json == (id)kCFNull) return nil;
該判斷的意思是,json對象不存在,或者為空是返回nil
。
獲取到字典后,調用字典轉Model方法:
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary
此方法也是在.h
文件中暴露出來的方法。接下來查看具體實現:
/**
字典轉model
@param dictionary 字典
@return 返回Model對象
*/
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
// 1. 驗證,空值,nil和是否是字典
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
Class cls = [self class];
// 2. 創建一個YYModelMeta對象
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
// 判斷是否需要自定義返回模型的類型,這是YYModel協議中的內容,算是附加功能暫時先忽略,后面在介紹。
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 3. 創建model對象
NSObject *one = [cls new];
// 4. 關鍵方法
if ([one yy_modelSetWithDictionary:dictionary])
return one;
return nil;
}
這個方法主要的任務是調用了yy_modelSetWithDictionary:
方法。這個方法也是在.h
文件中暴露的,它的作用是根據字典初始化模型。代碼實現:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
// 1. 值和類型驗證
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
// 2. 創建Modelmeta對象
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
// 屬性個數,如為零返回。創建失敗
if (modelMeta->_keyMappedCount == 0) return NO;
// YYModel協議,暫且忽略
if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}
// 3. 創建結構體
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
// 模型元值數量和字典數量關系
// 1. 通常情況是兩者相等,
// 2. 模型元鍵值少于字典個數
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
// 調用ModelSetWithDictionaryFunction方法,這是核心方法。
// 參數:
// 1. 要操作的字典
// 2. 為每個鍵值對調用一次的回調函數
// 3. 指針大小的程序定義的值,作為第三個參數傳遞給應用程序函數,但此函數未使用該值. 與參數2適應
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) {
// 每個keypath都執行ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
// 每個keypath都執行ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 忽略
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
上面注釋該方法大致干了幾件事。
YYModelMeta
對象ModelSetContext
結構體ModelSetWithDictionaryFunction
方法關于ModelSetContext
是一個結構體。包含模型元,模型實例和待處理字典。
typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
通過上面的分析,線路越來越清晰,下面看一下核心方法ModelSetWithDictionaryFunction
將字典的鍵值對取出賦值給Model。
// 獲取到字典的鍵值對,和上下文信息。
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
// 上下文,包含模型元,模型實例,字典
ModelSetContext *context = _context;
//模型元
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
//在模型元屬性字典中查找鍵值為key的屬性
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
// 模型實例
__unsafe_unretained id model = (__bridge id)(context->model);
// 核心內容,遍歷所有的屬性元。知道_next = nil
while (propertyMeta) {
if (propertyMeta->_setter) {
// 最終轉換(高潮)
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
該方法主要任務是,遍歷所有的屬性元,調用模型屬性賦值方法ModelSetValueForProperty
。
最終實現。也就是YYModel
的最最最核心的部分。不過,這個方法長的離譜。由于涉及到較多的編碼類型,需要對不同的類型區分處理,導致代碼過長。
由于ModelSetValueForPorperty
代碼較長,這里不再復制代碼。只梳理一下實現的邏輯,注釋代碼在Github自行查閱。
isCNumber
, nsType
和其他類型來區分處理。isCNumber
此時調用方法ModelSetNumberToPorperty
。該方法將NSNumber類型的值根據不同的編碼賦值給屬性。nsType
是YYEncodingNSType
枚舉類型,枚舉Foundation
所有的類型,對不同的類型進行處理。YYEncodingType
枚舉定義的情況。調用ModelSetNumberToPorperty
方法,該方法作用是:識別null
、bool
、123.23
、"123.45"
等類型,轉為NSNumber。
以上就是JSON轉Model全部過程。
相對JSON
轉Model
來說,Model
轉JSON
就簡單多了??梢允褂?code>NSJSONSerialization類將Foundation對象轉為JSON。但是轉為JSON必須滿足一下條件。
NSArray
或NSDictionary
NSString
,NSNumber
,NSArray
,NSDictionary
或者NSNull
NSString
NAN
或無窮大官方說明NSJSONSerialization文檔
接下來看看是如何轉換的,首先看到的方法是:
- (id)yy_modelToJSONObject;
該方法的時下很簡單只有幾行代碼。
- (id)yy_modelToJSONObject {
// 關鍵方法
id jsonObject = ModelToJSONObjectRecursive(self);
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
return nil;
}
內部調用了方法ModelToJSONObjectRecursive
方法。作者對該方法注釋中說,返回一個有效的JSON
對象(NSArray/NSDictionary/NSString/NSumber/NSNull)
或者為空。查看源碼,該方法做了幾件事
承接上文,本文對JSON轉Model主線路進行詳細的分析,關于一些輔助功能沒有分析。還有,一些代碼比較長,由于篇幅限制,所以注釋代碼放在《GitHub》。
由于筆者能力有限,不可避免的出現錯誤,歡迎大家指正。
個人博客Owenli
微博Owenli_千
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。