上篇博文底層原理篇(三)我們講到了類的編譯、連結、加載的過程,接下來我們去探索一下類與分類、拓展與分類、關聯對象等的内容!
1.類與分類
- 分類的結構
struct category_t {//仔細看一下,沒有ivar的清單哦!!! const char *name; //誰的分類 classref_t cls; //對應的類屬性 //以下是方法清單&屬性清單&協定清單 struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
這些屬性,方法,是怎麼和類産生關系的呢?下面我們來看看分類的加載!
我們要區分開類加載與分類加載的不同之處,這個地方很容易混淆!
- 這裡需要說明一下,我們的分類為什麼不能添加成員變量,這是因為分類結構中并沒有成員變量清單的存儲屬性,分類在運作時被attach到類,是對rw的處理,在class_rw_t *rw中,并沒有const ivar_list_t *ivars成員變量清單屬性!!!這個是在class_ro_t *ro中處理的!是以我們不能在分類中添加成員變量,但是可以添加屬性!!!
2.類拓展
延展類别又稱為拓展(Extension),拓展是分類的一種特例,也叫匿名分類:寫在.m中,沒有名稱!
還有一種寫法:直接建立一個File type為Extension的OC檔案!!也是我們的類拓展!
- 下面是兩種實作的舉例:
//1.在.m中 @interface WMPerson () //沒有名字,我們的分類在這裡的括号裡面是有名稱的 @property (nonatomic, copy) NSString *mName;//拓展的屬性 - (void)extM_method;//拓展的方法(.m實作) @end //2.建立一個File type為Extension的OC檔案WMPerson+WMExtension.h //其實和上面的一樣,隻是單獨成為一個檔案 @interface WMPerson () @property (nonatomic, copy) NSString *ext_name; @property (nonatomic, copy) NSString *ext_subject; - (void)extH_method;//在類的.m實作 @end
- 我們在read_images中看到分類的代碼處理邏輯,但是我們沒有看到拓展在哪裡處理的?
- 那麼我們該怎麼去分析這個過程呢?
- 我們知道,在read_images中,類加載最初始:
//Discover classes. 讀取所有的類資訊 在這之後才會根據load方法初始化 for (EACH_HEADER) { classref_t *classlist = _getObjc2ClassList(hi, &count); if (! mustReadClasses(hi)) { // Image is sufficiently optimized that we need not call readClass() continue; } bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); const class_ro_t *ro = (const class_ro_t *)cls->data(); const char *cname = ro->name; const char *oname = "WMPerson"; if (cname && (strcmp(cname, oname) == 0)) { printf("_getObjc2ClassList 類名 :%s - %p\n",cname,cls); } if (newCls != cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a future class. // Non-lazily realize the class below. resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } }
- 我們斷點斷在printf("_getObjc2ClassList 類名 :%s - %p\n",cname,cls);列印處,如果我們的這個類加載了,我們可以去看看他在編譯階段的ro都存了什麼!!!
//列印結果: (lldb) p ro (const class_ro_t *) $0 = 0x0000000100002320 (lldb) p *$0 (const class_ro_t) $1 = { flags = 388 instanceStart = 8 instanceSize = 40 reserved = 0 ivarLayout = 0x0000000100001eda "\x04" name = 0x0000000100001ed1 "WMPerson" baseMethodList = 0x0000000100002140 baseProtocols = 0x0000000000000000 ivars = 0x0000000100002250 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000022d8 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $1.baseMethodList (method_list_t *const) $2 = 0x0000000100002140 (lldb) p *$2 (method_list_t) $3 = { entsize_list_tt<method_t, method_list_t, 3> = { entsizeAndFlags = 24 count = 11 first = { name = "extM_method" types = 0x0000000100001f84 "[email protected]:8" imp = 0x0000000100001950 (objc-debug`-[WMPerson extM_method] at WMPerson.m:24) } } } (lldb) p $3.get(1) (method_t) $4 = { name = "extH_method" types = 0x0000000100001f84 "[email protected]:8" imp = 0x0000000100001980 (objc-debug`-[WMPerson extH_method] at WMPerson.m:28) } (lldb) p $3.get(2) (method_t) $5 = { name = ".cxx_destruct" types = 0x0000000100001f84 "[email protected]:8" imp = 0x0000000100001b70 (objc-debug`-[WMPerson .cxx_destruct] at WMPerson.m:18) } (lldb) p $3.get(3) (method_t) $6 = { name = "name" types = 0x0000000100001f8c "@[email protected]:8" imp = 0x00000001000019b0 (objc-debug`-[WMPerson name] at WMPerson.h:13) } (lldb) p $3.get(4) (method_t) $7 = { name = "setName:" types = 0x0000000100001f94 "[email protected]:[email protected]" imp = 0x00000001000019e0 (objc-debug`-[WMPerson setName:] at WMPerson.h:13) } (lldb) p $3.get(5) (method_t) $8 = { name = "mName" types = 0x0000000100001f8c "@[email protected]:8" imp = 0x0000000100001a20 (objc-debug`-[WMPerson mName] at WMPerson.m:12) } (lldb) p $3.get(6) (method_t) $9 = { name = "setMName:" types = 0x0000000100001f94 "[email protected]:[email protected]" imp = 0x0000000100001a50 (objc-debug`-[WMPerson setMName:] at WMPerson.m:12) } (lldb) p $3.get(7) (method_t) $10 = { name = "ext_name" types = 0x0000000100001f8c "@[email protected]:8" imp = 0x0000000100001a90 (objc-debug`-[WMPerson ext_name] at WMPerson+WMExtension.h:15) } (lldb) p $3.get(8) (method_t) $11 = { name = "setExt_name:" types = 0x0000000100001f94 "[email protected]:[email protected]" imp = 0x0000000100001ac0 (objc-debug`-[WMPerson setExt_name:] at WMPerson+WMExtension.h:15) } (lldb) p $3.get(9) (method_t) $12 = { name = "ext_subject" types = 0x0000000100001f8c "@[email protected]:8" imp = 0x0000000100001b00 (objc-debug`-[WMPerson ext_subject] at WMPerson+WMExtension.h:16) } (lldb) p $3.get(10) (method_t) $13 = { name = "setExt_subject:" types = 0x0000000100001f94 "[email protected]:[email protected]" imp = 0x0000000100001b30 (objc-debug`-[WMPerson setExt_subject:] at WMPerson+WMExtension.h:16) }
- 下标0和1分别是extM_method/extH_method兩個方法,以及還有下面的一些setter/getter!
- 總結:這裡我們看到,在加載這些類的時候,從ro中就能取出這些資料,說明是在編譯期就處理好的了!!!
- 我們斷點斷在printf("_getObjc2ClassList 類名 :%s - %p\n",cname,cls);列印處,如果我們的這個類加載了,我們可以去看看他在編譯階段的ro都存了什麼!!!
3.runtime關聯對象
我們使用分類,很多時候會用到關聯對象!
例如:我們在分類中,添加一個property屬性,我們上面知道,分類的操作是對類中rw的操作,屬性存儲在property_list_t數組中!但是,在rw中并沒有屬性的setter/getter!是以我們要在分類的.m中對分類進行屬性的關聯操作!
//在.h中添加cate_name屬性!
#import "WMPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface WMPerson (WMCategory)
@property (nonatomic, copy) NSString *cate_name;
@end
NS_ASSUME_NONNULL_END
//.m中對屬性的setter/getter添加
#import "WMPerson+WMCategory.h"
#import <objc/runtime.h>
@implementation WMPerson (WMCategory)
-(void)setCate_name:(NSString *)cate_name {
//關聯屬性
objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)cate_name{
return objc_getAssociatedObject(self, @"name");
}
@end
- 下面我們來看這個關聯對象的底層原了解析:
- 1.objc_setAssociatedObject()
- 1.上面如果不夠清楚,我們來畫個圖:
- 2.總結:上圖中,總表中包含了ObjectAssociationMap0、ObjectAssociationMap1等這樣的表,這些表的下标是根據對應的類的位址經過處理得到的下标!根據下标object,疊代器周遊擷取object對應的ObjectAssociationMap小表,再根據key,疊代器周遊ObjectAssociationMap小表,擷取到key對應的ObjcAssociation對象!這樣我們就找到了關聯對象!!!
- 2.objc_getAssociatedObject()
總結:取值的查找過程和存值基本一樣!
關聯對象的釋放是在調用dealloc過程中進行判斷bool assoc = obj->hasAssociatedObjects();如果assoc為YES,則說明有關聯對象,就會進行釋放:if (assoc) _object_remove_assocations(obj);
- 1.objc_setAssociatedObject()
以上就是分類,拓展以及關聯對象的一些底層知識,謝謝觀賞!!!