一直以為能夠讀懂源代碼是件很牛的事情,但是每次都被動輒復(fù)雜的語法的架構(gòu)嚇跑,在偶然看到一個叫Draveness的大牛寫的源代碼分析博客,耐著性子看完了一篇SDWebImage框架的分析,才發(fā)覺其實啃源代碼沒那么可怕,而且對于功力的提升十分顯著,尤其偶爾接觸到的底層的理解讓我豁然開朗,遂打算獨自寫一篇第三方框架的源代碼分析作為自己的第一篇技術(shù)博客.
MJExtension
A fast, convenient and nonintrusive conversion between JSON and model.
MJExtension是一套字典和模型之間互相轉(zhuǎn)換的超輕量級框架.
能做什么
- 字典(JSON)--> 模型(Model) CoreData模型(Core Data Model)
- JSON字符串(JSONString) --> 模型(Model)、CoreData模型(Core Data Model)
- 模型(Model)、CoreData模型(Core Data Model) --> 字典(JSON)
- JSON數(shù)組(JSON Array) --> 模型數(shù)組(Model Array)、CoreData模型數(shù)組(Core Data Model Array)
- JSON字符串(JSONString) --> 模型數(shù)組(Model Array)、CoreData模型數(shù)組(Core Data Model Array)
- 模型數(shù)組(Model Array)、CoreData模型數(shù)組(Core Data Model Array) --> 字典數(shù)組(JSON Array)
- 只需要一行代碼,就能實現(xiàn)模型的所有屬性進行Coding(歸檔和解檔)
字典 -> 模型
MJExtension提供了一個類方法進行字典轉(zhuǎn)模型的工作,我們通過對
+ (instancetype)mj_objectWithKeyValues:(id)keyValues方法解析,來開始本篇分析.下面讓我們打開這個方法的實現(xiàn)代碼NSObject+MJKeyValue.m.
當(dāng)然你也可以git clone github git@github.com:CoderMJLee/MJExtension.git到本地來看.
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
使用這個類方法的唯一作用就是調(diào)用了另一個方法
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個字典");
if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
return [[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self) inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
}
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
下面我們就仔細分析一下這個方法的實現(xiàn)過程.
兩個參數(shù)
- (id)keyValues 用來接收傳入的字典,將參數(shù)寫為id類型是為了能夠接收所有類型的參數(shù)
- (NSManagedObjectContext *)context (NSManagedObjectContext)是一個對于數(shù)據(jù)庫的封裝,只要能保存在數(shù)據(jù)庫中的內(nèi)容,都可以保存在NSManagedObjectContext中.
獲取對象
方法的第一步是接收傳入的對象
- (id)mj_JSONObject
{
if ([self isKindOfClass:[NSString class]]) {
return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
} else if ([self isKindOfClass:[NSData class]]) {
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}
return self.mj_keyValues;
}
如果傳入對象是NSString類型或者NSData類型,會使用系統(tǒng)自帶的數(shù)據(jù)類型轉(zhuǎn)換將對象轉(zhuǎn)換成JSON類型返回,否則返回自己.
構(gòu)建錯誤
第二行代碼使用了一個自定義的方法MJExtensionAssertError
#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
MJExtensionBuildError(clazz, msg); \
return returnValue;\
}
通過第一步的轉(zhuǎn)換,傳入的對象會是JSON類型或其他類型的一種,如果傳入的不是JSON類型,MJExtensionBuildError會調(diào)用runtime中的objc_setAssociatedObject方法為當(dāng)前類關(guān)聯(lián)一個NSError對象,用來生成錯誤日志.
在龐大的項目中難免會遇到傳值錯誤類型的低級錯誤,這個方法可以讓用戶在debug的過程中準(zhǔn)確找出錯誤位置并進行修改.
這里作者為clazz這個參數(shù)的定義是[self class],即自身類別.
context參數(shù)處理
接下來會進行context參數(shù)的處理
if ([self isSubclassOfClass:[NSManagedObject class]] && context) { return [[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self) inManagedObjectContext:context] mj_setKeyValues:keyValues context:context]; }
如果調(diào)用此方法的類別為NSManagedObject的子類而且傳入的context不為空,則會實例一個NSEntityDescription對象重新進行類型轉(zhuǎn)換.
實例化對象
return [[[self alloc] init] mj_setKeyValues:keyValues];
最后實例化一個self類型的對象進行類型轉(zhuǎn)換操作.
以上行為我稱之為是對參數(shù)的預(yù)處理,只有傳入正確或者通過處理后能夠進行類型轉(zhuǎn)換的參數(shù)后才能夠正確的進行類型轉(zhuǎn)換方法.
類型轉(zhuǎn)換
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context最后一步進入類型轉(zhuǎn)換處理,在這里進行的是JSON轉(zhuǎn)Model行為.
- (instancetype)mj_setKeyValues:(id)keyValues context:
(NSManagedObjectContext *)context```
由于這個方法的實現(xiàn)代碼太長,在這里就不再貼出全部代碼,后文會挑選關(guān)鍵代碼進行分析.