在學習Runtime的時候,你可能要脫離原來你所認知的區域,比如:你真的了解類和對象么?你真的理解實例方法和類方法么?你真的以為你看到的就是所有的東西么?網上的那些所謂的實用技巧你真的理解什么意思么?磨刀不誤砍柴工,我們先說一下很重要的幾個概念。
1. id 以及 Class
- (我姑且認為大家印象中:id就是對象,Class就是類。)
大家對于id
和Class
其實并不陌生,我們做一個實驗,創建一個Person
的類,然后創建一個Person
對象,然后這樣:
Person* person = [[Person alloc] init];
NSLog(@"%p",person);
//
NSLog(@"%p",[person class]);
NSLog(@"%p",[Person class]);
//
NSLog(@"%p",object_getClass(person));
NSLog(@"%p",object_getClass([person class]));
打印結果:
RuntimeSkill[2048:247155] 0x60000000ed30
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6a8
我擦嘞,就問你懵逼了沒有?
從結構看功能
寫一個Class
去看系統的API:
typedef struct objc_class *Class;
同時你會發現有一個這個東西typedef struct objc_object *id;
發現id
是一個結構體,并且里面只有一個Class
類型的指針isa:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
再看Class
其實也是系統定義的一個結構體,只不過結構復雜很多:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; //父類
const char *name OBJC2_UNAVAILABLE; //類名
long version OBJC2_UNAVAILABLE; //版本信息
long info OBJC2_UNAVAILABLE; //類信息
long instance_size OBJC2_UNAVAILABLE; //實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; //成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; //方法鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; //方法緩存(大幅提高方法調用效率)
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; //協議鏈表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
通過對比我們可以確認幾點:
- 在Objective-C中,所有的類其實也是一個對象,我們可以認為
id
中的isa指針指向的是一個類對象,并且在Class
結構體中的isa指針指向元類(后面做解釋)。 - 在Runtime中,以
obj_
開頭的方法大多是針對id
類型的對象進行操作,而以class_
開頭方法主要是針對Class
類型的類對象進行操作。 - Runtime的以
class_
開頭的方法是針對與Class
結構體的各個元素進行操作的。 - 結構體元素的作用詳見注釋,不再多余贅述。
元類(Meta Class)
在objc_object
結構體中,有一個objc_class
類型的指針,奇怪的是,在objc_class
結構體中又有一個objc_class
類型的指針,這也就引入了元類的概念。
首先,元類是類對象的類,同樣也就是一個對象,它的結構也是objc_class
結構,有人現在會說了,你這不是扯淡嗎?這么說那元類的isa
指針有指向哪里?我讀書少,你不要騙我!!!
其實這就涉及了一種特殊的機制,為了不讓這種結構無限延伸下去,Objective-C讓所有的元類的isa
指針指向基類的元類,以此作為它們的所屬類。即,任何NSObject繼承體系下的元類都使用NSObject的元類作為自己的所屬類,而基類的元類的isa
指針是指向它自己,并且基類的元類的父類是基類。這樣就形成了一個完美的閉環。太他媽有想法了:
這樣我們就可以解釋剛開始的打印結果了,第一個地址
0x60000000ed30
為創建的Person
對象,第二,第三,第四個地址0x10702c6d0
為Person
類對象的地址,最后一個0x10702c6a8
為Person
類的元類的地址。(注意:元類的調用只能用object_getClass ()
或者objc_getClass ()
獲得,使用類對象調用class
方法是無法獲取到元類的,它只是返回當前類對象而已。)
實例方法和類方法
下面我們在方法調用方面來研究一下元類存在的必然性,那就要說到實例方法和類方法發送消息的機制,在Class
的結構體中有一個元素methodLists
,當我們調用一個方法時,系統會在這個列表中進行查找(這里不考慮cache
),同樣元類也有一個methodLists
,所以:
- 當給對象發送消息時(調用實例方法),系統會在當前對象對應的類對象的
methodLists
中進行查找。 - 當給類發送消息時(調用類方法),系統會在當前類的元類的
methodLists
中進行查找。
也就是說類對象存儲著一個類的所有實例方法,元類存儲著一個類的所有類方法。同時每個類都會有一個單獨的元類,因為每個類的類方法基本不可能完全相同。看到這有人睡說,元類中還有很多的元素,比如成員變量的鏈表,那類變量怎么說?目前我沒有找到關于類變量的信息。
2. SEL、Method、IMP
關于SEL
相信大家都很熟悉,但是對于Method
和IMP
就相對陌生了,下面我們詳解這幾個關于方法的數據類型。
SEL
SEL
是系統在編譯過程中,會根據方法的名字以及參數序列生成一個用來區分這個方法的唯一ID編號,這個 ID 就是 SEL
類型的。我們需要注意的是,只要方法的名字和參數序列完全相同,那么它們的 ID編號就是相同的。
獲取SEL的幾種方法:
SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);
打印結果:
RuntimeSkill[1741:214328] 0x10957b985___0x10957b985___0x10957b985
Method
Method
從字面上一看就是方法的意思。Method
其實就是 objc_method
的結構體指針,結構如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //參數類型以及返回值類型編碼
IMP method_imp OBJC2_UNAVAILABLE; //方法實現指針
}
獲取Method
的方法:
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
IMP
IMP
即Implementation
,為指向函數實現的指針,如果我們能夠獲取到這個指針,則可以直接調用該方法,充分證實了它就是一個函數的指針。
獲取IMP
的方法:
//通過Method獲取IMP
IMP method_getImplementation(Method m);
// 返回方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
獲取到IMP
之后可直接調用方法:
SEL aSel = @selector(didReceiveMemoryWarning);
Method method = class_getInstanceMethod([self class], aSel);
IMP imp = method_getImplementation(method);
((void (*) (id, SEL)) (void *)imp)(self, aSel);
3. Ivar
在Class
結構體中,有一個ivars
的鏈表結構,其中存儲著所有變量信息(Ivar
的數組),每一個Ivar
指針對應一個變量元素。同時通過系統的API,我們看到Ivar
也是一個結構體,`typedef struct objc_ivar *Ivar,它也是一個結構題:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
對于
Ivar`的操作有以下方法:
// 獲取類中指定名稱成員變量
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類變量
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- 需要注意的是:
class_copyIvarList
這個函數函數,返回全部實例變量的數組,數組中每個Ivar
指向該成員變量信息的objc_ivar
結構體的指針。這個數組不包含在父類中聲明的變量。outCount指針返回數組的大小。我們必須使用free()來釋放這個數組。 - 關于類變量的傳說連聽過都沒聽過,你要是吹牛逼說你知道,那麻煩您教一下我,必有重謝,哈哈哈哈哈。
總結
對于上面的很多的示例代碼只是提供給大家幫助理解的,我提醒你一點:開發中千萬不要這么寫代碼,不然你升職加薪,迎娶白富美,走上人生巔峰本來就不可能,現在可能連溫飽也是個問題了。這也不是單純的扯淡,明確概念之后,我們講Runtime的實際用法才事半功倍。