YYModel源碼閱讀
1.Demo簡(jiǎn)要介紹: 只有2個(gè)實(shí)現(xiàn)文件冕屯,NSObject+YYModel 和 YYClassInfo
1.普通簡(jiǎn)直映射稼虎,NSObject的擴(kuò)展:
+ (instancetype)modelWithJSON:(id)json;
我們需要自己聲明json中的key值,key值的類型可以是任何類型忙菠,如嵌套一個(gè)model叠殷。
2.自定義映射方式。如果json中的key值并不是你期望的key值窗市,可以使用以下方法做一個(gè)映射:
+ (NSDictionary *)modelCustomPropertyMapper;
返回的字典中,value是json中的key值饮笛,字典的key是生成的model的屬性买喧。
3.model的一些擴(kuò)展屬性棺牧,包括:
Coding\Copying\hash\equal
2.學(xué)習(xí)準(zhǔn)備
我們從方法調(diào)用的時(shí)序去了解它內(nèi)部的原理缕题。但是在這之前绕辖,我們需要做一些準(zhǔn)備工作。準(zhǔn)備了一份objc4的源碼无午,我們了解YYClassInfo時(shí)會(huì)用到一些知識(shí)媒役。
ivar: 定義對(duì)象的實(shí)例變量
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
property: 屬性
struct property_t {
const char *name;
const char *attributes;
};
method: SEL是選擇子,是方法名的唯一標(biāo)識(shí)符宪迟。IMP是方法實(shí)現(xiàn)的指針酣衷。
struct method_t {
SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
元類meta class
建議閱讀: Objective-C 中的元類(meta class)是什么? 和 唐巧: Objective-C對(duì)象模型及應(yīng)用
簡(jiǎn)單的說(shuō)次泽,元類是類對(duì)象的類穿仪。我們知道,實(shí)例對(duì)象的方法存儲(chǔ)在類中意荤。而類對(duì)象的方法啊片,是存儲(chǔ)在元類中的,這也使得Object-C的對(duì)象模型得到了統(tǒng)一玖像。
3.源碼學(xué)習(xí)
1.YYClassInfo
json數(shù)據(jù)只是字符串紫谷,離我們使用的多元的數(shù)據(jù)類型差距較大,更何況還有屬性等修飾捐寥。YYClassInfo正如其名笤昨,就是對(duì)類信息的一層封裝,并且提供了很多公有的屬性上真,便于我們?nèi)ヅ渲靡б浮V饕校?/p>
1.YYClassIvarInfo
2.YYClassPropertyInfo
3.YYClassMethodInfo
查看這幾個(gè)類的公有屬性,再對(duì)比上文學(xué)習(xí)準(zhǔn)備objc4中的源碼實(shí)現(xiàn)睡互,不難看出它主要把結(jié)構(gòu)體中對(duì)我們不可見(jiàn)的成員開(kāi)放了出來(lái)。并且自身實(shí)現(xiàn)了YYEncodingType枚舉,用于區(qū)分編碼類型(后文說(shuō)明)就珠。我們?cè)倏碮YClassInfo的聲明寇壳,包含了類,父類妻怎,元類和上文提及的類的信息壳炎。
查看他的構(gòu)造方法:
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls];
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
dispatch_semaphore_signal(lock);
}
}
return info;
}
創(chuàng)建了2個(gè)緩存區(qū),classCache和metaCache逼侦,分別用于存放元類和類匿辩。由于可變字典的創(chuàng)建不是線性安全的,所以使用了信號(hào)量榛丢。信號(hào)量可以參考這里铲球。然后,從緩存區(qū)獲取info晰赞,如果獲取失敗則創(chuàng)建一個(gè)info稼病,并將info存入緩存區(qū)。init方法也很簡(jiǎn)單:
- (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;
}
我們注意到內(nèi)部有一個(gè)_update方法掖鱼,顧名思義就是更新info的屬性的值然走。
_YYModelPropertyMeta (NSObject+YYModel.m中)
如注釋所說(shuō),這是一個(gè)關(guān)于model的property信息的類戏挡。需要注意的一點(diǎn)是聲明的成員變量列表末尾有一個(gè)next指針芍瑞,結(jié)合構(gòu)造方法可以知道該meta類的對(duì)象是用鏈表的方式組合在一起的。
/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
//...略
}
它只有一個(gè)構(gòu)造方法褐墅,做的事是:把傳入的classInfo拆檬,propertyInfo,generic(映射關(guān)系表)等信息全部組裝到了YYModelPropertyMeta中掌栅。用來(lái)干啥呢秩仆?后文繼續(xù)~
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo
propertyInfo:(YYClassPropertyInfo *)propertyInfo
generic:(Class)generic;
YYModelMeta
如YYModelPropertyMeta,YYModelMeta就是一個(gè)關(guān)于model的class信息的類猾封。構(gòu)造方法內(nèi)的注釋也非常清晰澄耍。簡(jiǎn)單提及一下,具體的講解將根據(jù)實(shí)際場(chǎng)景分析晌缘。請(qǐng)記住這5個(gè)步驟齐莲,后文我將用1~5來(lái)舉例這五步。
- (instancetype)initWithClass:(Class)cls {
//1.獲取黑名單
//2.獲取白名單
//3.獲取容器屬性的映射關(guān)系表
//4.將YYClassInfo實(shí)例中的所有YYClassPropertyInfo加入到字典中
//5.創(chuàng)建一個(gè)新的映射表
}
4.具體應(yīng)用場(chǎng)景
取YYKit Demo中最簡(jiǎn)單的例子(即SimpleObjectExample)磷箕。
調(diào)用modelWithJSON后选酗,JSON會(huì)用系統(tǒng)的json解析器解析為字典,并傳入metaWithClass中岳枷。
①先查看metaWithClass內(nèi)部的流程:
1)在metaWithClass中芒填,初次創(chuàng)建model時(shí)從緩存獲取meta失敗呜叫,則調(diào)用initWithClass生成一個(gè)YYModelMeta。
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
//...
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
if (!meta || meta->_classInfo.needUpdate) {
meta = [[_YYModelMeta alloc] initWithClass:cls];
//...
}
//...
}
2)在YYModelMeta的initWithClass中殿衰,先根據(jù)參數(shù)cls生成YYClassInfo類classInfo朱庆,用classInfo方便我們對(duì)數(shù)據(jù)處理。
3)簡(jiǎn)單的model闷祥,會(huì)直接調(diào)用第四部(前文提及)娱颊。
這里主要是把classInfo中的property信息取出,生成一個(gè)YYModelPropertyMeta實(shí)例凯砍。然后全部存入allPropertyMetas數(shù)組中箱硕。插問(wèn)一句,為什么property這么重要悟衩?因?yàn)樗俏覀兊膍odel提供對(duì)外的屬性剧罩,對(duì)應(yīng)了JSON數(shù)據(jù)中的字典。是鏈接JSON數(shù)據(jù)和model的橋梁(我亂比喻~)局待。
4)非自定義的key值斑响,直接遍歷存入哈希表中
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
propertyMeta->_mappedToKey = name;
propertyMeta->_next = mapper[name] ?: nil;
mapper[name] = propertyMeta;
}];
5)最后是一些布爾值成員變量的賦值。經(jīng)過(guò)以上的步驟钳榨,我們成功的拿到了modelMeta舰罚。
6#)這只是對(duì)最基本的JSON數(shù)據(jù)類型的解析,YYModelMeta的初始化方法里面還有對(duì)自定義鍵值的處理薛耻,還是值得我們?nèi)ド钊肓私獾挠铡1疚臅簳r(shí)先分析到這。
②再看modelSetWithDictionary方法中做啥
1)緩存中可以獲取到moedelMeta
2)提供了一個(gè)ModelSetContext
typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
3)首先說(shuō)一下CFArrayApplyFunction這個(gè)方法饼齿,字典同理:
void CFArrayApplyFunction(CFArrayRef theArray,
CFRange range,
CFArrayApplierFunction CF_NOESCAPE applier,
void *context);
在給定的range中遍歷theArray饲漾,執(zhí)行applier方法。對(duì)應(yīng)到Y(jié)YModel的代碼缕溉,我們看下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) {
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
根據(jù)propertyMeta遍歷,通過(guò)ModelSetValueForProperty方法給屬性的賦值证鸥。ModelSetValueForProperty方法中通過(guò)swich-case對(duì)不同類型的值一一賦值僚楞。最終,我們得到了想要的model枉层!
5.回顧(個(gè)人的總結(jié)泉褐,僅供參考)
YYModel對(duì)外的API非常簡(jiǎn)單明了,但是內(nèi)部高度使用了CFFoundation框架鸟蜡,大量使用了指針膜赃、結(jié)構(gòu)體等C語(yǔ)言層面的內(nèi)容,都是希望在性能上表現(xiàn)更好揉忘。它把類跳座、元類中許多沒(méi)有對(duì)外開(kāi)放的內(nèi)容對(duì)外開(kāi)放端铛,封裝,方便了定制化的操作躺坟。
學(xué)習(xí)的過(guò)程中沦补,需要對(duì)OC對(duì)象模型有較好的了解乳蓄,對(duì)CFFoundation有一定的掌握咪橙,這些都更能加深我們對(duì)OC這么語(yǔ)言的認(rèn)知,很好虚倒!
全文只是個(gè)人的理解美侦,有問(wèn)題的地方希望指正,交流魂奥!