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