model和json轉(zhuǎn)換時開發(fā)中普遍使用的魔招,在iOS開發(fā)中系統(tǒng)KVO本身也提供了簡單的API:
同時常用流行的model和json的轉(zhuǎn)化框架有MJExtension、Mantle五辽、JSONModel办斑、YYModel等。今天主要就YYModel得源碼做一個剖析杆逗,延續(xù)之前風(fēng)格乡翅,還是通過一個典型的應(yīng)用展開分析。
首先讓我們看一下這個框架的基本結(jié)構(gòu):
可以看到這是一個很輕量簡潔的工具罪郊,只有四個文件蠕蚜,其中NSObject+YYModel是NSObject的categary提供了相應(yīng)的使用接口,和Mantle相比沒有侵入性悔橄,YYClassInfo類是針對NSObject的Ivar靶累、Property、Method進(jìn)行封裝的類癣疟,之后具體分析挣柬。
下邊先看一個調(diào)用:
以上是YYKit的demo中一個簡單實(shí)例。
之下就從json轉(zhuǎn)model的API開始分析:
由NSDictionary *dic = [self _yy_dictionaryWithJSON:json]可以看出睛挚,由于傳入的json是id類型邪蛔,所以首先要將其轉(zhuǎn)化為NSDictionary*類型,然后就變了字典轉(zhuǎn)模型扎狱。先具體分析一下這個將json轉(zhuǎn)化為字典的方法:
可看出如果json為空侧到,直接返回的dic也是空勃教,如果json是字典直接返回,如果是字符串則調(diào)用系統(tǒng)方法jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]將其轉(zhuǎn)化為NSData*類型的jsonData匠抗,如果json是NSData*類型則直接將json返給jsonData荣回,然后調(diào)用系統(tǒng)方法dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]將jsonData轉(zhuǎn)化為字典dic并返回。
接下來看這個將字典轉(zhuǎn)化為model的方法:[self modelWithDictionary:dic]:
如果傳入?yún)?shù)dictionary為空或不是字典戈咳,返nil心软。
Class cls = [self class]返回類本身。
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]創(chuàng)建了一個_YYModelMeta*類型的對象modelMeta著蛙。
接下來就從這個初始化工廠方法入手删铃,詳細(xì)分析_YYModelMeta這個類:
方法前半部分說明,cache是在程序運(yùn)行期間只生成一次的靜態(tài)變量踏堡,lock在這里是用信號量實(shí)現(xiàn)的安全鎖猎唁,從_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls))可以看出cache是在程序運(yùn)行期間緩存每個cls對應(yīng)的_YYModelMeta *類型的meta對象。
所以這個方法就是如果全局還沒有cache顷蟆,則創(chuàng)建cache诫隅,并創(chuàng)建安全鎖lock,然后從cache中獲取以cls為key值對應(yīng)的_YYModelMeta*類型的對象帐偎,如果沒有或?qū)ο笮枰聞t調(diào)用meta = [[_YYModelMeta alloc] initWithClass:cls]創(chuàng)建meta對象逐纬,并插入到cache中,整個過程通過lock鎖保證了線程安全削樊。
在深入探討具體的初始化成員方法:
可以看出這是一個很龐大的方法豁生。YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]這一步是闖將一個YYClassInfo*類型的對象classInfo,開篇提到過YYClassInfo是用來存儲一些類信息的漫贞,現(xiàn)在就具體深入到這個方法中:
類似于_YYModelMeta的工廠方法甸箱,先在緩存中查YYClassInfo *info,如果沒有迅脐,則調(diào)用info = [[YYClassInfo alloc] initWithClass:cls]方法創(chuàng)建芍殖,創(chuàng)建完后將其存入相應(yīng)的緩存中(classCache或metaCache)。
再深入探查YYClassInfo的具體初始化方法:
在這個方法中依次保存了@property (nonatomic, assign, readonly) Class cls谴蔑,@property (nullable, nonatomic, assign, readonly) Class superCls豌骏,@property (nullable, nonatomic, assign, readonly) Class metaCls,@property (nonatomic, strong, readonly) NSString *name這些屬性树碱。
再看[self _update]這個方法做了什么:
可以看出這個方法主要是為@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos肯适,@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos,@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos這三個屬性賦值成榜。
其中_ivarInfos存儲了類的所有成員變量框舔,_methodInfos類的所有成員方法,_propertyInfos存儲了類的所有屬性。
以_methodInfos為類:
先是通過runtime方法Method*methods =class_copyMethodList(cls, &methodCount)獲取類的所有方法刘绣,然后遍歷方法樱溉,針對每個方法創(chuàng)建一個YYClassMethodInfo *類型的info:
可以看出YYClassMethodInfo保存了方法名、方法選擇子纬凤、函數(shù)指針福贞、參數(shù)類型等,然后調(diào)用方法if(info.name) methodInfos[info.name] = info以方法名字為key值停士,以YYClassMethodInfo *info為value值挖帘,將所有方法存進(jìn)_methodInfos字典中。_ivarInfos和_propertyInfos類似恋技,也都是用字典存儲了所有的屬性和成員變量拇舀。最后設(shè)置_needUpdate = NO。
再回到YYClassInfo的- (instancetype)initWithClass:(Class)cls方法中:_superClassInfo = [self.class classInfoWithClass:_superCls]再依次創(chuàng)建模型父類的YYClassInfo*類型的對象并保存在緩存中蜻底。
接著回到_YYModelMeta的- (instancetype)initWithClass:(Class)cls方法中骄崩。
統(tǒng)計(jì)黑名單和白名單。
這一步是獲取容器屬性中的類型信息薄辅。
這一步遍歷之前生成的propertyInfos要拂,先根據(jù)黑名單和白名單做相應(yīng)的過濾,針對需要在模型轉(zhuǎn)化中應(yīng)用的屬性進(jìn)行一個_YYModelPropertyMeta*類型的封裝站楚,然后保存在allPropertyMetas中脱惰,并將allPropertyMetas字典中的所有value值賦給_allPropertyMetas屬性。
如果模型自定義的屬性和字典的key值不一致的話源请,這一步是建立一個影射(需要在模型中實(shí)現(xiàn)modelCustomPropertyMapper方法枪芒,在這個方法中返回映射字典)。然后將相關(guān)影射信息存在_mapper屬性中谁尸。
最后對_YYModelMeta*類型的meta進(jìn)行賦值,然后返回纽甘。
就此良蛮,_YYModelMeta類的- (instancetype)initWithClass:(Class)cls方法結(jié)束。
返回到+ (instancetype)metaWithClass:(Class)cls方法中:
將_YYModelMeta*meta放入緩存中悍赢,這個方法也結(jié)束决瞳。
經(jīng)過如此繁雜的操作,你是否已經(jīng)迷路了呢左权,現(xiàn)在回到字典轉(zhuǎn)模型這一步了:
上面的操作就是_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]這一步皮胡,這一步就是將模型類的相關(guān)屬性、成員變量赏迟、方法屡贺、黑名單、白名單、模型屬性和字典索引之間影射等信息取出來并全局緩存甩栈,在下次調(diào)用的時候直接獲取可以大大提高運(yùn)行速度泻仙。
下面就開始分析將字典轉(zhuǎn)化為模型的方法:
先創(chuàng)建一個模型對象,再通過調(diào)用方法[one modelSetWithDictionary:dictionary]給對象賦值:
在這個方法中調(diào)用CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,? CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction,? &context)方法賦值量没。
CFArrayApplyFunction對第一個數(shù)組類型的參數(shù)在第二個CFRange類型參數(shù)區(qū)間中遍歷玉转,沒遍歷一次調(diào)用一次第三個函數(shù)指針類型的參數(shù)指向的函數(shù),第四個參數(shù)為調(diào)用的函數(shù)的第二個參數(shù)殴蹄,每遍歷一次的數(shù)組元素為函數(shù)的第一個參數(shù)究抓。
可以看出第三個參數(shù)指向的函數(shù)式具體的對每一個屬性賦值的函數(shù),可展開來看:
就此袭灯,回到:
整個字典轉(zhuǎn)模型結(jié)束刺下。
之上就是一個字典轉(zhuǎn)模型的主干過程,整個過程還有很多細(xì)節(jié)都很贊妓蛮,整個框架在安全性和效率性兼顧做的確實(shí)很棒怠李,對于一些細(xì)節(jié),后邊再繼續(xù)探索蛤克。