YYModel源碼學(xué)習(xí)

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)題的地方希望指正,交流魂奥!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菠剩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耻煤,更是在濱河造成了極大的恐慌具壮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哈蝇,死亡現(xiàn)場(chǎng)離奇詭異棺妓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)炮赦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)怜跑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吠勘,你說(shuō)我怎么就攤上這事性芬。” “怎么了剧防?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵植锉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我峭拘,道長(zhǎng)俊庇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任棚唆,我火速辦了婚禮暇赤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宵凌。我一直安慰自己鞋囊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布瞎惫。 她就那樣靜靜地躺著溜腐,像睡著了一般译株。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挺益,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天歉糜,我揣著相機(jī)與錄音,去河邊找鬼望众。 笑死匪补,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烂翰。 我是一名探鬼主播夯缺,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甘耿!你這毒婦竟也來(lái)了踊兜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤佳恬,失蹤者是張志新(化名)和其女友劉穎捏境,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體毁葱,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垫言,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了头谜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骏掀。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖柱告,靈堂內(nèi)的尸體忽然破棺而出截驮,到底是詐尸還是另有隱情,我是刑警寧澤际度,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布葵袭,位于F島的核電站,受9級(jí)特大地震影響乖菱,放射性物質(zhì)發(fā)生泄漏坡锡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一窒所、第九天 我趴在偏房一處隱蔽的房頂上張望鹉勒。 院中可真熱鬧,春花似錦吵取、人聲如沸禽额。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脯倒。三九已至实辑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藻丢,已是汗流浹背剪撬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悠反,地道東北人残黑。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像问慎,于是被迫代替她去往敵國(guó)和親萍摊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理如叼,服務(wù)發(fā)現(xiàn),斷路器穷劈,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 導(dǎo)語(yǔ):YYModel庫(kù)是優(yōu)秀的模型轉(zhuǎn)換庫(kù)笼恰,可自動(dòng)處理模型轉(zhuǎn)換(從JSON到Model 和 Model到JSON)的...
    南華coder閱讀 5,455評(píng)論 0 11
  • 概述 ? iOS源碼解析—YYModel(YYClassInfo)分析了如何根據(jù)OC的Class對(duì)象構(gòu)建...
    egoCogito_panf閱讀 11,579評(píng)論 4 32
  • 如何集成? 支持CocoaPods歇终,在 Podfile 中添加 pod 'YYModel'社证。 支持Carthage...
    勇往直前888閱讀 10,973評(píng)論 0 7
  • 終于把前面的base文件夾簡(jiǎn)簡(jiǎn)單單的看了一遍,終于可以回到正片上來(lái)了评凝,保證不爛尾追葡。 項(xiàng)目天天用yymodel解析數(shù)...
    充滿活力的早晨閱讀 1,368評(píng)論 1 0