YYModel源碼分析(一)

本文章所使用的YYModel源碼基于0.9.8版本。
從截圖來看,YYModel是由兩個類構成,本章先著手分析YYClassInfo,該類比較簡單,主要使用runtime來獲取類的屬性、成員變量、方法的相關信息。

DingTalk20160620171225.png

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

  • YYClassIvarInfo:類成員變量相關信息;
  • YYClassMethodInfo:類方法相關信息;
  • YYClassPropertyInfo:類屬性相關信息;
  • YYClassInfo:類相關信息,由上邊三個類加一些其他信息組成;

YYClassIvarInfo

從類名可以看出該類存儲了類成員變量的相關信息,該類由五條只讀屬性和一個實例化方法構成。五條屬性如下圖所示:

DingTalk20160620184733.png

除了YYEncodingType類型的屬性其它都是簡單的調用runtime方法取得的,那這個YYEncodingType到底是什么呢?點擊該類型可以看到其為一枚舉類型,枚舉了所有的類型編碼、方法編碼和屬性關鍵字,關于類型編碼和屬性關鍵字的更多信息請查看官網NSHipster。這里使用了NS_OPTIONS而未使用NS_ENUM,關于兩者的區別請點擊這里type屬性的實現如下:

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

通過調用YYEncodingGetType方法傳入類型編碼獲得。

YYClassMethodInfo

該類存儲了方法的相關信息,包括方法名、SEL、IMP、方法類型、返回值類型、參數類型數組。

    // 方法結構體
    _method = method;
    // SEL
    _sel = method_getName(method);
    // IMP
    _imp = method_getImplementation(method);
    // 方法名
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    // 方法的參數和返回類型
    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);
    }
    // 方法的參數
    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

該類存儲了屬性的相關信息,包括屬性結構體、屬性名、編碼類型、成員變量名、遵守的協議等。在類的實現中可以看到objc_property_attribute_t結構體,點到頭文件看看它的聲明是這樣的。

DingTalk20160621095431.png

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

@property (nonatomic, copy) NSString *name;

其name和value為

DingTalk20160621100421.png

T表示屬性的類型;C表示Copy,N表示nonatomic,這兩個是屬性的修飾符;V表示屬性所對應的成員變量。可以看到屬性的修飾符通常是沒有值的,包括retain,assgin,atomic等。在該類的實現中只有類型編碼的分支較為復雜,下面分析一下:

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];
        // 找不到 @" 停止本次循環
        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
        
        // 屬性的類
        NSString *clsName = nil;
        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
        }
        
        // 屬性所遵守的協議,屬性可遵守多個協議,
        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的相應屬性,如果沒有定制的賦值和取值操作,手動實現一下。

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

YYClassInfo

類的信息,其實就是上邊三個類的一個集合加上一些其他的信息組成。

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

關于_update方法并沒有什么好講的,就是runtime的簡單應用,稍微有一些基礎的都能看懂,關于元類在runtime相關的文章中都能看到,已經被講爛了。

+ (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];
    }
    // 發送信號
    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內部做了信號量處理,為線程安全的;當類的內部結構變化后,例如使用class_addMethod()添加一個方法,你需要調用setNeedUpdate(),在needUpdate返回YES之后重新調用``+ (instancetype)classInfoWithClass:(Class)cls`來獲取類的最新信息。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,707評論 18 399
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,578評論 33 466
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,043評論 6 13
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • 最近用了一個星期的時間閱讀了這本《賴聲川的創意學》,市面上大多講創意的書都是講技巧,唯有這本書講的是創意的本質。此...
    一葉知秋V閱讀 551評論 0 1