YYModel源碼分析(一)

本文章所使用的YYModel源碼基于0.9.8版本鼓黔。
從截圖來看,YYModel是由兩個類構(gòu)成不见,本章先著手分析YYClassInfo澳化,該類比較簡單,主要使用runtime來獲取類的屬性稳吮、成員變量缎谷、方法的相關(guān)信息。

DingTalk20160620171225.png

YYClassInfo一共包含有四個類灶似,如下圖所示:
DingTalk20160620171130.png

  • YYClassIvarInfo:類成員變量相關(guān)信息列林;
  • YYClassMethodInfo:類方法相關(guān)信息;
  • YYClassPropertyInfo:類屬性相關(guān)信息酪惭;
  • YYClassInfo:類相關(guān)信息希痴,由上邊三個類加一些其他信息組成;

YYClassIvarInfo

從類名可以看出該類存儲了類成員變量的相關(guān)信息春感,該類由五條只讀屬性和一個實(shí)例化方法構(gòu)成砌创。五條屬性如下圖所示:

DingTalk20160620184733.png

除了YYEncodingType類型的屬性其它都是簡單的調(diào)用runtime方法取得的,那這個YYEncodingType到底是什么呢鲫懒?點(diǎn)擊該類型可以看到其為一枚舉類型纺铭,枚舉了所有的類型編碼、方法編碼和屬性關(guān)鍵字刀疙,關(guān)于類型編碼和屬性關(guān)鍵字的更多信息請查看官網(wǎng)NSHipster。這里使用了NS_OPTIONS而未使用NS_ENUM扫倡,關(guān)于兩者的區(qū)別請點(diǎn)擊這里谦秧。type屬性的實(shí)現(xiàn)如下:

 const char *typeEncoding = ivar_getTypeEncoding(ivar);
 if (typeEncoding) {
     _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
     _type = YYEncodingGetType(typeEncoding);
 }

通過調(diào)用YYEncodingGetType方法傳入類型編碼獲得竟纳。

YYClassMethodInfo

該類存儲了方法的相關(guān)信息,包括方法名疚鲤、SEL锥累、IMP、方法類型集歇、返回值類型桶略、參數(shù)類型數(shù)組。

    // 方法結(jié)構(gòu)體
    _method = method;
    // SEL
    _sel = method_getName(method);
    // IMP
    _imp = method_getImplementation(method);
    // 方法名
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    // 方法的參數(shù)和返回類型
    const char *typeEncoding = method_getTypeEncoding(method);
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
    }
    // 方法的返回類型
    char *returnType = method_copyReturnType(method);
    if (returnType) {
        _returnTypeEncoding = [NSString stringWithUTF8String:returnType];
        free(returnType);
    }
    // 方法的參數(shù)
    unsigned int argumentCount = method_getNumberOfArguments(method);
    if (argumentCount > 0) {
        NSMutableArray *argumentTypes = [NSMutableArray new];
        for (unsigned int i = 0; i < argumentCount; i++) {
            char *argumentType = method_copyArgumentType(method, i);
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;

YYClassPropertyInfo

該類存儲了屬性的相關(guān)信息诲宇,包括屬性結(jié)構(gòu)體际歼、屬性名、編碼類型姑蓝、成員變量名鹅心、遵守的協(xié)議等。在類的實(shí)現(xiàn)中可以看到objc_property_attribute_t結(jié)構(gòu)體纺荧,點(diǎn)到頭文件看看它的聲明是這樣的旭愧。

DingTalk20160621095431.png

簡單的包含了name和value。name屬性名宙暇;value屬性的值输枯,通常為空。通過例子來解釋下其意思占贫,對于一個如下屬性:

@property (nonatomic, copy) NSString *name;

其name和value為

DingTalk20160621100421.png

T表示屬性的類型桃熄;C表示Copy,N表示nonatomic靶剑,這兩個是屬性的修飾符蜻拨;V表示屬性所對應(yīng)的成員變量∽可以看到屬性的修飾符通常是沒有值的缎讼,包括retain,assgin坑匠,atomic等血崭。在該類的實(shí)現(xiàn)中只有類型編碼的分支較為復(fù)雜,下面分析一下:

if (attrs[i].value) {
    // 類型編碼
    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
    type = YYEncodingGetType(attrs[i].value);
    // 如果屬性類型為對象
    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
        // 掃描屬性類型字符串
        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
        // 找不到 @" 停止本次循環(huán)
        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
        
        // 屬性的類
        NSString *clsName = nil;
        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
        }
        
        // 屬性所遵守的協(xié)議厘灼,屬性可遵守多個協(xié)議座掘,
        NSMutableArray *protocols = nil;
        while ([scanner scanString:@"<" intoString:NULL]) {
            NSString* protocol = nil;
            if ([scanner scanUpToString:@">" intoString: &protocol]) {
                if (protocol.length) {
                    if (!protocols) protocols = [NSMutableArray new];
                    [protocols addObject:protocol];
                }
            }
            [scanner scanString:@">" intoString:NULL];
        }
        _protocols = protocols;
    }
}

還有比較重要的setter和getter賦值绰寞。

case 'G': {
    type |= YYEncodingTypePropertyCustomGetter;
    if (attrs[i].value) {
        _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
    }
} break;
case 'S': {
    type |= YYEncodingTypePropertyCustomSetter;
    if (attrs[i].value) {
        _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
    }
} 

如果有定制的setter和getter方法,直接賦值給YYClassPropertyInfo的相應(yīng)屬性,如果沒有定制的賦值和取值操作邓馒,手動實(shí)現(xiàn)一下。

if (_name.length) {
    if (!_getter) {
        _getter = NSSelectorFromString(_name);
    }
    if (!_setter) {
        _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
    }
}

YYClassInfo

類的信息怕篷,其實(shí)就是上邊三個類的一個集合加上一些其他的信息組成。

- (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;
}

關(guān)于_update方法并沒有什么好講的钻洒,就是runtime的簡單應(yīng)用,稍微有一些基礎(chǔ)的都能看懂锄开,關(guān)于元類在runtime相關(guān)的文章中都能看到素标,已經(jīng)被講爛了。

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    // 類信息緩存萍悴,Class為key头遭,YYClassInfo為value
    static CFMutableDictionaryRef classCache;
    // 元類信息緩存,Class為key癣诱,YYClassInfo為value
    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));
    // info存在且需要更新
    if (info && info->_needUpdate) {
        [info _update];
    }
    // 發(fā)送信號
    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;
}

類信息和元類信息都做了緩存字典计维,key為Class,value為YYClassInfo狡刘;+ (instancetype)classInfoWithClass:(Class)cls內(nèi)部做了信號量處理享潜,為線程安全的;當(dāng)類的內(nèi)部結(jié)構(gòu)變化后嗅蔬,例如使用class_addMethod()添加一個方法剑按,你需要調(diào)用setNeedUpdate(),在needUpdate返回YES之后重新調(diào)用``+ (instancetype)classInfoWithClass:(Class)cls`來獲取類的最新信息澜术。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艺蝴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸟废,更是在濱河造成了極大的恐慌猜敢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盒延,死亡現(xiàn)場離奇詭異缩擂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)添寺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門胯盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人计露,你說我怎么就攤上這事博脑。” “怎么了票罐?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵叉趣,是天一觀的道長。 經(jīng)常有香客問我该押,道長疗杉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任蚕礼,我火速辦了婚禮乡数,結(jié)果婚禮上椭蹄,老公的妹妹穿的比我還像新娘。我一直安慰自己净赴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布罩润。 她就那樣靜靜地躺著玖翅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪割以。 梳的紋絲不亂的頭發(fā)上金度,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音严沥,去河邊找鬼猜极。 笑死,一個胖子當(dāng)著我的面吹牛消玄,可吹牛的內(nèi)容都是我干的跟伏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翩瓜,長吁一口氣:“原來是場噩夢啊……” “哼受扳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兔跌,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤勘高,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坟桅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體华望,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年仅乓,在試婚紗的時候發(fā)現(xiàn)自己被綠了赖舟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡方灾,死狀恐怖建蹄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情裕偿,我是刑警寧澤洞慎,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站嘿棘,受9級特大地震影響劲腿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸟妙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一焦人、第九天 我趴在偏房一處隱蔽的房頂上張望挥吵。 院中可真熱鬧,春花似錦花椭、人聲如沸忽匈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丹允。三九已至,卻和暖如春袋倔,著一層夾襖步出監(jiān)牢的瞬間雕蔽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工宾娜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留批狐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓前塔,卻偏偏與公主長得像嚣艇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘱根,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法髓废,類相關(guān)的語法,內(nèi)部類的語法该抒,繼承相關(guān)的語法慌洪,異常的語法,線程的語...
    子非魚_t_閱讀 31,624評論 18 399
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,554評論 33 466
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 10,962評論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理凑保,服務(wù)發(fā)現(xiàn)冈爹,斷路器,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 最近用了一個星期的時間閱讀了這本《賴聲川的創(chuàng)意學(xué)》欧引,市面上大多講創(chuàng)意的書都是講技巧频伤,唯有這本書講的是創(chuàng)意的本質(zhì)。此...
    一葉知秋V閱讀 540評論 0 1