1. 預備知識
首先每一個instance即例項物件都有一根指標叫isa_t指標,指向物件對應的class,每一個class都有一個對應的isa_t指標指向class對應的元類。為什麼要搞出元類這個東西呢? 關於這個問題我們自己不難理解,我們可以想一下我們每次呼叫一個類的例項方法和類方法,這些方法都分別儲存在哪個地方呢?是不是例項方法就儲存在例項中,類方法就儲存在類中呢?
其實我們只要仔細想一下就發現這種方式是行不通的,每一個例項中的例項方法都試一樣的,如果每一個例項都要儲存一次方法,那豈不是造成了相當多的資源浪費? 於是就有了這麼一個東西,下一層的方法儲存在上一層中,我們只要把例項方法儲存在class中,類方法儲存在類的元類中,這樣每次要找對應的方法,直接去找對應的上一級,這樣更加高效而且更加節省資源。關於詳細的資料請參考參考資料
2. YYClassInfo
YYClassInfo是對Class的一個封裝,將class一些常用的資訊進行了快取,提高了命中率和訪問效率,我們先看原始碼來理解
1 2 3 4 5 |
/** class的描述資訊.對一個class進行封裝 */ @interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; /// *ivarInfos; /// *methodInfos; /// *propertyInfos; /// |
在YYClassInfo宣告的方法裡,我們可以看到YYClassInfo一旦class進行了修改,就要做對應的更新,這樣的操作是為了能夠高效快取class的資訊,如ivars,method,property,我們首先從這個類的入口方法開始看起
2.1 入口
1 2 3 4 5 6 7 |
// 建立入口,從一個className中獲取Class來獲取class內容 + (instancetype)classInfoWithClassName:(NSString *)className { // 取得className對應的class Class cls = NSClassFromString(className); // 呼叫classInfoWithClass return [self classInfoWithClass:cls]; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// 通過class獲取對應class的資訊,並且對這些資訊進行處理 + (instancetype)classInfoWithClass:(Class)cls { if (!cls) return nil; // class 快取容器 static CFMutableDictionaryRef classCache; // metaclass 快取容器 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); // 是元類還是Class,從結果的容器中進行查詢 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不在快取容器中 // 通過initWithClass重新建立一個info info = [[YYClassInfo alloc] initWithClass:cls]; if (info) { // 快取到對應的容器中 dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 再一次判斷是meta還是class CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info)); dispatch_semaphore_signal(lock); } } return info; } |
這裡有幾點需要說明一下:
- mutable不是執行緒安全的,所以這裡需要建立鎖
- 建立快取容器,如果在快取容器中直接找到了class,則直接獲取到對應的ivar,method,property,這樣在下次訪問到的時候就不用再去找
- 如果沒有獲取到,就開始建立一個YYClassInfo獲得class中的描述資訊
2.2 建立YYClassInfo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (instancetype)initWithClass:(Class)cls { // 判斷cls的合法性 if (!cls) return nil; self = [super init]; // cls _cls = cls; // superClass _superCls = class_getSuperclass(cls); // 判斷是否為meta _isMeta = class_isMetaClass(cls); if (!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls)); } // 獲得類名 _name = NSStringFromClass(cls); [self _update]; // 獲得_superInfo _superClassInfo = [self.class classInfoWithClass:_superCls]; return self; } |
這裡呼叫到了_update更新資訊的介面
2.3 更新
1 2 3 4 |
@implementation YYClassInfo { // 是否需要重新整理 BOOL _needUpdate; } |
YYClassInfo維護了一個變數來判斷是否需要重新整理,通過呼叫setNeedUpdate來修改_needUpdate=YES,進而呼叫_update方法來進行重新整理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (void)_update { // 重新整理ivar,method,property資訊 _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; // 取得類中的methods unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { // 建立dictionary來進行快取method資訊 NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; // 遍歷methods for (unsigned int i = 0; i |
3. 小結
說到這裡關於YYClassInfo的程式碼已經分析完了,在這個類中,YY對於Ivar,property和Method都進行了一層封裝,最後都服務於YYClassInfo,這樣做的一個好處就是把原來裡層的內容暴露在外層,方便查詢,也可以進行快取,提高訪問效率和命中率,這也是對後面進行json轉換做的一些工作
4.最後
接下來會對核心類NSObject+YYModel進行研讀,一路讀下來發現寫程式碼就該像YYModel這樣有規範,有匠心精神。最近想研究一下快取的機制,希望能好好讀一下YYCache從中學習。