目錄:
1. 什么是Runtime?
2. 與 Runtime 系統(tǒng)進(jìn)行交互
3. Runtime 類與對(duì)象
-
- 1 類與對(duì)象基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
- 3.1.1. 類對(duì)象(class object)
- 3.1.2. 實(shí)例對(duì)象(id/objc_object)
- 3.1.3. 元類(Metaclass)
-
3.2. 類與對(duì)象操作函數(shù)
- 3.2.1. 類相關(guān)操作函數(shù)
- 3.2.1.1. 針對(duì)類的相關(guān)操作函數(shù)
- 3.2.1.2. 獲取類定義
- 3.2.1.3. 成員變量(ivars)及屬性
- 3.2.1.4 方法(methodLists)
- 3.2.1.5. 協(xié)議(objc_protocol_list)
- 3.2.1.6. 其它
- 3.2.1.7. 實(shí)例(Example)
- 3.2.2. 動(dòng)態(tài)創(chuàng)建類和對(duì)象
- 3.2.3. 實(shí)例操作函數(shù)
- 3.2.4. 獲取類定義
- 3.2.1. 類相關(guān)操作函數(shù)
4. 消息
- 4.1. 獲得方法地址
- 4.2. 消息發(fā)送機(jī)制(objc_msgSend函數(shù) )
- 4.3. 使用隱藏的參數(shù)
5. 消息轉(zhuǎn)發(fā)
- 5.1. 動(dòng)態(tài)方法解析 (resolveInstanceMethod:)
- 5.2. 備用接收者 (forwardingTargetForSelector:)
- 5.3. 完整的消息轉(zhuǎn)發(fā)
6. 消息轉(zhuǎn)發(fā)和多重繼承
7. 消息轉(zhuǎn)發(fā)和類繼承
第一章.什么是Runtime
Objective-C 語(yǔ)言 將很多操作盡可能的從 編譯和鏈接時(shí)推遲到運(yùn)行時(shí)。只要有可能,Objective-C 總是使用動(dòng)態(tài)的方式來(lái)解決問題。這意味著 Objective-C 語(yǔ)言不僅需要一個(gè)編譯器,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯好的代碼。這兒的運(yùn)行時(shí)系統(tǒng)扮演的角色類似于 Objective-C 語(yǔ)言的操作系統(tǒng),Objective-C 基于該系統(tǒng)來(lái)工作。
運(yùn)行時(shí)系統(tǒng)是一個(gè)公開接口的動(dòng)態(tài)庫(kù),有一些數(shù)據(jù)結(jié)構(gòu)和函數(shù)的集合組成,這些數(shù)據(jù)結(jié)構(gòu)和函數(shù)的聲明 頭文件 存放于 /usr/include/objc目錄下,這些函數(shù)支持用純C的函數(shù)來(lái)實(shí)現(xiàn)和Objective-C同樣的功能。還有一些函數(shù)構(gòu)成了NSObject 類方法的基礎(chǔ)。這些函數(shù)使得訪問運(yùn)行時(shí)系統(tǒng)接口和提供開發(fā)工具成為可能,這意味著我們使用時(shí)只需要引入objc/Runtime.h頭文件即可
常見面試題:
1.說(shuō)說(shuō)什么是runtime?平時(shí)項(xiàng)目中有用過么?
2.了解runtime嗎?是什么?
參考答案:(
自己總結(jié)的,若有誤或有好的答案,請(qǐng)留言)
runtime簡(jiǎn)稱運(yùn)行時(shí),是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制,由一些數(shù)據(jù)結(jié)構(gòu)和函數(shù)的集合組成,是一套純C寫的API。Objective-C 基于運(yùn)行時(shí)系統(tǒng)來(lái)工作。因?yàn)镺bjective-C 語(yǔ)言允許很多操作盡可能的從編譯和鏈接時(shí)推遲到運(yùn)行時(shí)。只要有可能,Objective-C 總是使用動(dòng)態(tài)的方式來(lái)解決問題。這意味著 Objective-C 語(yǔ)言不僅需要一個(gè)編譯器,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯好的代碼。這兒的運(yùn)行時(shí)系統(tǒng)扮演的角色類似于Objective-C語(yǔ)言的操作系統(tǒng)。
3.為什么需要Runtime?
參考答案:(
自己總結(jié)的,若有誤或有好的答案,請(qǐng)留言)
Objective-C 是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,它會(huì)將一些工作放在代碼運(yùn)行時(shí)才處理而并非編譯時(shí)。而在運(yùn)行時(shí),我們所編寫的OC代碼會(huì)轉(zhuǎn)換成完整的運(yùn)行時(shí)代碼。OC的函數(shù)調(diào)用稱為消息發(fā)送,屬于動(dòng)態(tài)調(diào)用過程。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。因此,編譯器是不夠的,我們還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(Runtime system)來(lái)處理編譯后的代碼。
第二章.與 Runtime 系統(tǒng)進(jìn)行交互
Objective-C 程序有三種途徑和運(yùn)行時(shí)系統(tǒng)交互:
- 通過Objective-C源代碼
- 通過類NSObject的方法
- 通過運(yùn)行時(shí)系統(tǒng)的函數(shù)
-
2.1 通過Objective-C源代碼
大部分情況下,運(yùn)行時(shí)系統(tǒng)在后臺(tái)自動(dòng)運(yùn)行,我們只需編寫和編譯 Objective-C 源代碼。當(dāng)您編譯 Objective-C 類 和 方法時(shí),編譯器為實(shí)現(xiàn)語(yǔ)言動(dòng)態(tài)特性將自動(dòng)創(chuàng)建一些 數(shù)據(jù)結(jié)構(gòu) 和 函數(shù)。這些數(shù)據(jù)結(jié)構(gòu)包含 類定義和 協(xié)議類定義中的信息,如在Objective-C 2.0 程序設(shè)計(jì)語(yǔ)言中定義類和協(xié)議類一節(jié)所討論 的 類的對(duì)象和 協(xié)議類的對(duì)象,方法選標(biāo),實(shí)例變量模板,以及其它來(lái)自于源代碼的信息。
運(yùn)行時(shí)系統(tǒng)的主要功能就是根據(jù)源代碼中的表達(dá)式發(fā)送消息,如"消息”一節(jié)所述。
-
2.2 通過類NSObject的方法
Cocoa程序中絕大部分類都是NSObject類的子類,所以大部分都繼承了NSObject類的方法,因而繼承 了NSObject的行為。(NSProxy類是個(gè)例外;更多細(xì)節(jié)參考““消息轉(zhuǎn)發(fā)”一節(jié)。)然而,某些情況下,NSObject類 僅僅 定義了完成某件事情的模板,而沒有提供所有需要的代碼。例如,NSObject 類定義了 description方法,返回該類內(nèi)容的字符串表示,該方法主要是用來(lái)調(diào)試程序 (GDB 中的 print-object 方法就是直接打印出該方法返回的字符串)。NSObject 類中 該方法(description)的實(shí)現(xiàn) 并不知道子類中的內(nèi)容,所以它只是返回類的名字和對(duì)象的地址。那 NSObject的子類可以重新實(shí)現(xiàn)該方法,以提供更多的信息。例如,NSArray 類改寫了該方法來(lái)返回 NSArray 類包含的每個(gè)對(duì)象的內(nèi)容。
還有一些 NSObject 的方法只是簡(jiǎn)單地從 Runtime 系統(tǒng)中獲取信息,從而允許對(duì)象進(jìn)行一定程度的自我檢查。例如:
- -
class方法 : 返回對(duì)象的類; -
-isKindOfClass:和-isMemberOfClass:方法 : 檢查對(duì)象是否存在于指定的類的繼承體系中(也可以說(shuō)成是判斷 是否是其子類或者父類或者當(dāng)前類的成員變量); -
-respondsToSelector:: 檢查對(duì)象能否響應(yīng)指定的消息; -
-conformsToProtocol:: 檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法; -
-methodForSelector:: 返回指定方法實(shí)現(xiàn)的地址。
-
2.3 通過運(yùn)行時(shí)系統(tǒng)的函數(shù)
盡管大部分情況下運(yùn)行時(shí)系統(tǒng)的函數(shù)在 Objective-C 程序不是必須的,但是有時(shí)候?qū)τ?Objecitve-C 程序來(lái)說(shuō)某些函數(shù)是非常有用的。 這些函數(shù)的文檔參見 Objective-C Runtime Reference,也就是 Runtime API 文檔。
第三章. Runtime 類與對(duì)象
3.1 類與對(duì)象基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
-
3.1.1. 類對(duì)象(
class object)
Objective-C的類是由Class 類型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針。在 objc.h 和 runtime.h 中找到對(duì) class 的定義如下:
typedef struct objc_class *Class;
Class 是一個(gè) objc_class 結(jié)構(gòu)類型的指針;那 objc_class 又是怎樣一個(gè)結(jié)構(gòu)體呢?且看:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
objc_class 結(jié)構(gòu)體的各成員介紹如下:
isa:是一個(gè) objc_class 類型的指針,它指向metaClass(元類)。所有的類自身也是一個(gè)對(duì)象,那對(duì)象是對(duì)象,類自身也是對(duì)象,是不是有點(diǎn)混淆?
別急,Objective-C 中 一個(gè)術(shù)語(yǔ)來(lái)區(qū)分這兩種不同的對(duì)象:
- <1>
類對(duì)象(class object)與<2>實(shí)例對(duì)象(instance object)。
Objective-C還對(duì)類對(duì)象與實(shí)例對(duì)象中的isa所指向的類結(jié)構(gòu)作了不同的命名:
- 1>.
類對(duì)象(objc_class)中的isa指向類結(jié)構(gòu)被稱作元類(Metaclass),元類(Metaclass)就是類對(duì)象的類,每個(gè)類都有自己的元類,每個(gè)元類又有自己的isa,metaclass存儲(chǔ)類的static類成員變量與static類成員方法(+開頭的方法),這也是Objective-C的類方法使用元類的根本原因; - 2>.
實(shí)例對(duì)象中的 isa指向類結(jié)構(gòu)稱作class(普通的),class 結(jié)構(gòu)存儲(chǔ)類的普通成員變量與普通成員方法(-開頭的方法**)。
super_class:指向該類的父類!如果該類已經(jīng)是最頂層的根類(如 NSObject 或 NSProxy),那么 super_class 就為 NULL。
struct objc_super {
//指定類的實(shí)例.
__unsafe_unretained _Nonnull id receiver;
//指定特定的消息實(shí)例的超類.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class是第一個(gè)要搜索的類 */
};
name:一個(gè) C 字符串,指示類的名稱。我們可以在運(yùn)行期,通過這個(gè)名稱查找到該類(通過:id objc_getClass(const char *aClassName))或該類的 metaclass(id objc_getMetaClass(const char *aClassName));
version:類的版本信息,默認(rèn)初始化為 0。我們可以在運(yùn)行期對(duì)其進(jìn)行修改(class_setVersion)或獲?。╟lass_getVersion)。
info:類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)。有如下一些位掩碼:
CLS_CLASS (0x1L) 表示該類為普通 class ,其中包含實(shí)例方法和變量;
CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
CLS_INITIALIZED (0x4L) 表示該類已經(jīng)被運(yùn)行期初始化了,這個(gè)標(biāo)識(shí)位只被 objc_addClass 所設(shè)置;
CLS_POSING (0x8L) 表示該類被 pose 成其他的類;(poseclass 在ObjC 2.0中被廢棄了);
CLS_MAPPED (0x10L) 為ObjC運(yùn)行期所使用
CLS_FLUSH_CACHE (0x20L) 為ObjC運(yùn)行期所使用
CLS_GROW_CACHE (0x40L) 為ObjC運(yùn)行期所使用
CLS_NEED_BIND (0x80L) 為ObjC運(yùn)行期所使用
CLS_METHOD_ARRAY (0x100L) 該標(biāo)志位指示 methodlists 是指向一個(gè) objc_method_list 還是一個(gè)包含 objc_method_list 指針的數(shù)組;
instance_size:該類的實(shí)例變量大小(包括從父類繼承下來(lái)的實(shí)例變量);
ivars:指向 objc_ivar_list的指針,存儲(chǔ)每個(gè)實(shí)例變量的內(nèi)存地址,如果該類沒有任何實(shí)例變量則為 NULL;
//該類的成員變量鏈表,是objc_ivar_list結(jié)構(gòu)體指針
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
Ivar是表示實(shí)例變量的類型,其實(shí)際是一個(gè)指向objc_ivar結(jié)構(gòu)體的指針,其定義如下:
//變量結(jié)構(gòu)體---名稱,類型,偏移字節(jié)和占用的空間
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE; //名稱
char * _Nullable ivar_type OBJC2_UNAVAILABLE; //變量類型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
int space OBJC2_UNAVAILABLE; //占用的空間
#endif
}
methodLists:與 info 的一些標(biāo)志位有關(guān),CLS_METHOD_ARRAY 標(biāo)識(shí)位決定其指向的東西(是指向單個(gè) objc_method_list還是一個(gè) objc_method_list 指針數(shù)組),如果 info 設(shè)置了 CLS_CLASS則 objc_method_list 存儲(chǔ)實(shí)例方法,如果設(shè)置的是 CLS_META 則存儲(chǔ)類方法;
方法鏈表結(jié)構(gòu)體:
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
對(duì)象的每個(gè)方法的結(jié)構(gòu)體,SEL是方法選擇器,是HASH后的值,可以通過這個(gè)值找到函數(shù)體的實(shí)現(xiàn),IMP 是函數(shù)指針
struct objc_method {
SEL _Nonnull method_name //SEL是方法選擇器 OBJC2_UNAVAILABLE;
char *_Nullable method_types //方法類型(參數(shù)列表),
// `method_types `是個(gè)char 指針, `存儲(chǔ)`方法的`參數(shù)類型`和`返回值類型`。 OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp //method_imp 指向了`方法的實(shí)現(xiàn)`,
//本質(zhì)是一個(gè)`函數(shù)指針`。 OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE
cache:指向 objc_cache 的指針,用來(lái)緩存最近使用的方法,以提高效率。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí),它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中,這個(gè)對(duì)象只有一部分方法是常用的,很多方法其實(shí)很少用或者根本用不上。這種情況下,如果每次消息來(lái)時(shí),我們都是methodLists中遍歷一遍,性能勢(shì)必很差。這時(shí),cache就派上用場(chǎng)了。在我們每次調(diào)用過一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,提高了調(diào)用的效率
它用于緩存調(diào)用過的方法。這個(gè)字段是一個(gè)指向objc_cache結(jié)構(gòu)體的指針,其定義如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
該結(jié)構(gòu)體的字段描述如下:
- mask:一個(gè)整數(shù),指定
分配的緩存bucket的總數(shù)。在方法查找過程中,Objective-C runtime 使用這個(gè)字段來(lái)確定開始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))。這可以作為一個(gè)簡(jiǎn)單的hash散列算法。 - occupied:一個(gè)整數(shù),指定
實(shí)際占用的緩存bucket的總數(shù)。 - buckets:指向
Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)。
protocols:指向 objc_protocol_list 的指針,存儲(chǔ)該類聲明要遵守的正式協(xié)議。
-
3.1.2. 實(shí)例對(duì)象(
id/objc_object)
id指向一個(gè)類的實(shí)例對(duì)象(objc_object)的指針。id被定義在 objc/objc.h 目錄下,它的數(shù)據(jù)結(jié)構(gòu)是:
typedef struct objc_object *id;
其中 objc_object的底層定義
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到,iOS中很重要的id實(shí)際上就是objc_object的指針.而NSObject的第一個(gè)對(duì)象就是Class類型的isa。因此 id可以標(biāo)示所有基于NSObject的對(duì)象。對(duì)象可以通過 isa 指針找到其所屬的類。isa 是一個(gè) Class 類型的成員變量。
這樣,當(dāng)我們向一個(gè)Objective-C對(duì)象發(fā)送消息時(shí),Runtime會(huì)根據(jù)實(shí)例對(duì)象的isa指針找到這個(gè)實(shí)例對(duì)象所屬的類。然后,Runtime會(huì)在類的方法列表或者是父類的方法列表中去尋找與消息對(duì)應(yīng)的selector指向的方法,找到后即運(yùn)行這個(gè)方法。
注意: isa 指針在代碼運(yùn)行時(shí)并不 總指向?qū)嵗龑?duì)象所屬的類型,所以不能依靠它來(lái)確定類型,要想確定類型還是需要用對(duì)象的 -class 方法。
-
3.1.3. 元類(
Metaclass)
meta-class是一個(gè)類對(duì)象的類。當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表(-號(hào)方法列表)中查找方法;而向一個(gè)類發(fā)送消息時(shí),會(huì)在這個(gè)類的meta-class的方法列表(+號(hào)方法列表)中查找。
meta-class之所以重要,是因?yàn)樗?code>存儲(chǔ)著一個(gè)類的所有類方法(+號(hào)方法列表)。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
再深入一下,meta-class也是一個(gè)類,也可以向它發(fā)送一個(gè)消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無(wú)限延伸下去, ObjC的設(shè)計(jì)者讓所有的meta-class的isa指向基類(·根類·)的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類(·根類·)的meta-class的isa指針是指向它自己。這樣就形成了一個(gè)完美的閉環(huán)。
通過上面的描述,再加上對(duì)objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了。子類,父類,根類(這些都是普通 class)以及其對(duì)應(yīng)的 metaclass 的isa 與 super_class 之間關(guān)系 如下圖所示
- 規(guī)則一:類的
實(shí)例對(duì)象的 isa指向該類;該類的 isa指向該類的 metaclass;
(理解:Class的objc_object的isa指向該 Class 類,該類(objc_class)的isa指向Class metaClass`) - 規(guī)則二:類的
super_class指向其父類,如果該類為根類則值為NULL;
(理解:Class的super_class指向該Class的父類,如果該類Class是最頂層的類,那么該Class的super_class值為NULL) - 規(guī)則三:
metaclass的isa指向根 metaclass,如果該metaclass是根 metaclass則指向自身;
(理解:不管是子類還是父類的metaclass的isa一律 都指向根 metaclass,如果該 metaclass 是根 metaclass則指向自身) - 規(guī)則四:
metaclass的super_class指向父 metaclass,如果該 metaclass 是根 metaclass則指向該 metaclass對(duì)應(yīng)的類,也就是指向根類而不是自身metaclass;
注意:
根元類的superclass不是nil而是根類。對(duì)于ObjC原生的類,根元類的父類就是系統(tǒng)的根類NSOject。但根類不一定是NSObject,因?yàn)楹竺娼榻B的objc_allocateClassPair函數(shù)也可創(chuàng)建出一個(gè)根類。
那么 class 與 metaclass 有什么區(qū)別呢?
class是instance object 的類 類型。當(dāng)我們向實(shí)例對(duì)象發(fā)送消息(實(shí)例方法)時(shí),我們?cè)谠?code>實(shí)例對(duì)象的 class 結(jié)構(gòu)的 methodlists方法列表 中去查找響應(yīng)的函數(shù),如果沒找到匹配的響應(yīng)函數(shù)則在該 class 的父類中的 methodlists 方法列表去查找(查找鏈為上圖的中間那一排)。如下面的代碼中,向str 實(shí)例對(duì)象發(fā)送 lowercaseString 消息,會(huì)在 NSString 類結(jié)構(gòu)(NSString 的objc_class)的 methodlists 方法列表中去查找lowercaseString 的響應(yīng)函數(shù)。
NSString * str;
[str lowercaseString];
metaclass 是 class object 的類 類型 。當(dāng)我們向類對(duì)象發(fā)送消息(類方法)時(shí),我們?cè)谠?code>類對(duì)象的 metaclass(元類) 結(jié)構(gòu)的 methodlists 中去查找響應(yīng)的函數(shù),如果沒有找到匹配的響應(yīng)函數(shù)則在該 metaclass 的父類中的 methodlists去查找(查找鏈為上圖的最右邊那一排)。如下面的代碼中,向 NSString 類對(duì)象發(fā)送 stringWithString 消息,會(huì)在 NSString 的 metaclass 類結(jié)構(gòu)的 methodlists 中去查找 stringWithString 的響應(yīng)函數(shù)。
[NSString stringWithString:@"str"]; //stringWithString 是 + 號(hào)方法
好,至此我們明白了類的結(jié)構(gòu)層次,來(lái)看張圖會(huì)更明顯:
總結(jié):
ObjC 為每個(gè)類的定義生成兩個(gè) objc_class ,一個(gè)即普通的 class,另一個(gè)即 metaclass。我們可以在運(yùn)行時(shí)創(chuàng)建 這兩個(gè) objc_class 數(shù)據(jù)結(jié)構(gòu),然后使用 objc_addClass 動(dòng)態(tài)地創(chuàng)建新的類定義。**這個(gè)夠動(dòng)態(tài)夠強(qiáng)大的吧?
3.2. 類與對(duì)象操作函數(shù)
runtime提供了大量的函數(shù)來(lái)操作類與對(duì)象。類的操作方法大部分是以class_為前綴的,而對(duì)象的操作方法大部分是以objc_或object_為前綴。下面我們將根據(jù)這些方法的用途來(lái)分類討論這些方法的使用。
- 3.2.1. 類相關(guān)操作函數(shù)
我們可以回過頭去看看objc_class的定義,runtime提供的操作類的方法主要就是針對(duì)這個(gè)結(jié)構(gòu)體中的各個(gè)字段的。下面我們分別介紹這一些的函數(shù)。并在最后以實(shí)例來(lái)演示這些函數(shù)的具體用法。
3.2.1.1 針對(duì)類進(jìn)行操作的函數(shù)
針對(duì)類進(jìn)行操作的函數(shù)主要有:
/**
*返回類的名稱。
*
* @param cls一個(gè)類對(duì)象。
* @return 類的名稱,如果 cls是 Nil,則為空字符串。
*/
//獲取類的類名
const char * class_getName ( Class cls );
/**
*返回類的超類。
* @param cls一個(gè)類對(duì)象。
* @return類的超類,或者Nil if cls是根類,如果cls是Nil,則為Nil。
* @note你 通常應(yīng)該使用NSObject的超類方法 ,而不是這個(gè)函數(shù)。
*/
// 獲取類的父類
Class class_getSuperclass ( Class cls );
/**
*返回一個(gè)布爾值,指示類對(duì)象是否是元類。
* @param cls一個(gè)類對(duì)象。
* @return YES如果cls是元類,如果cls是非元類,則為NO,如果cls為Nil,則為NO。
*/
// 判斷給定的Class是否是一個(gè)元類
BOOL class_isMetaClass ( Class cls );
//返回類的實(shí)例大小
size_t
class_getInstanceSize(Class _Nullable cls);
/**
*返回給定類的指定 實(shí)例變量的Ivar 。
*
* @param cls 您希望獲取其實(shí)例變量的類。
* @param name 要獲取的實(shí)例變量定義的名稱。
* @return 指向包含有關(guān)信息的Ivar數(shù)據(jù)結(jié)構(gòu)的指針
* 由name指定的實(shí)例變量。
*/
// 返回給定類的指定實(shí)例變量的Ivar
Ivar class_getInstanceVariable(Class cls, const char * name);
/**
*返回給定類的指定 類變量 的Ivar。
*
* @param cls 您希望獲取其 類變量 的類定義。
* @param name 要獲取的 類變量 定義的名稱。
* @return 返回一個(gè)指向Ivar數(shù)據(jù)結(jié)構(gòu)的指針,該結(jié)構(gòu)包含有關(guān)name指定的 類變量 的信息。
*/
// 返回 給定類的指定類變量的Ivar
Ivar class_getClassVariable ( Class cls, const char *name );
/**
*向類添加新的實(shí)例變量。
*
* @ cls 你要操作的類名
* @ name 成員變量名稱
* @ size 開辟字節(jié)長(zhǎng)度
* @ alignment 對(duì)齊方式
* @ types 參數(shù)類型 “@”
* @return 如果成功添加了實(shí)例變量,則為YES,否則為NO
*(例如,該類已包含具有該名稱的實(shí)例變量)。
* @note: 只能在 objc_allocateClassPair 之后和 objc_registerClassPair
之前調(diào)用此函數(shù)。不支持 將實(shí)例變量添加到 現(xiàn)有類。
* @note : 該類 不能是元類 。 不支持將實(shí)例變量添加到元類。
* @note:實(shí)例變量的最小對(duì)齊字節(jié)數(shù)為1 << align。 實(shí)例的最小對(duì)齊方式
*變量取決于ivar的類型和機(jī)器架構(gòu)。
*對(duì)于任何指針類型的變量,傳遞log2(sizeof(pointer_type))。
*/
// 向類添加新的實(shí)例變量
BOOL class_addIvar ( Class cls, const char *name, size_t size,
uint8_t alignment, const char *types );
/**
*描述類聲明的 實(shí)例變量列表。
*
* @param cls要操作類。
* @param outCount返回時(shí),包含返回?cái)?shù)組的長(zhǎng)度。
*如果outCount為NULL,則不返回長(zhǎng)度。
* @return 類型為 Ivar的指針數(shù)組 ,描述類聲明的實(shí)例變量。
* 不包括超類 聲明的 任何實(shí)例變量。 該數(shù)組包含* outCount
*指針后跟一個(gè)NULL終止符。 您必須使用free() 釋放數(shù)組。
*如果類聲明沒有實(shí)例變量,或者cls為Nil,則返回NULL并且* outCount為0。
*/
// 獲取整個(gè)實(shí)例變量列表,獲取的不僅有@property聲明的變量還有不是@property聲明的變量。
Ivar * class_copyIvarList(Class cls, unsigned int * outCount) ;
/**
*設(shè)置給定類的Ivar布局。
*
* @param cls要修改的類。
* @param layout cls的 Ivars的布局。
*/
void class_setIvarLayout ( Class cls, const uint8_t *layout );
/**
*返回給定類的 Ivar布局的描述。
*
* @param cls要檢查的類。
* @return cls的 Ivar布局的描述。
*/
const uint8_t * class_getIvarLayout ( Class cls );
/**
*為給定的類設(shè)置弱Ivars的布局。
*
* @param cls要修改的類。
* @param layout cls的弱Ivars布局。
*/
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );
/**
*返回給定類的弱Ivars布局的描述。
*
* @param cls要檢查的類。
* @return 對(duì) cls的弱 Ivars布局的描述。
*/
const uint8_t * class_getWeakIvarLayout ( Class cls );
/**
*返回具有給定類的給定名稱的屬性。
*
* @param cls您要操作類。
* @param name 要操作的屬性的名稱。
* @return 返回描述屬性的 objc_property_t 類型的 指針,
如果類沒有聲明具有該名稱的屬性,則為NULL;如果cls為Nil,則為NULL。
*/
// 返回具有給定類的給定名稱的屬性
objc_property_t class_getProperty ( Class cls, const char *name );
/**
描述 類聲明 的 屬性列表。
*
* @param cls是您要操作的類。
* @param *outCount ,輸出指針,指明返回objc_property_t類型數(shù)組的大小。
*如果 outCount 為 NULL,則 不返回長(zhǎng)度。
*@返回 objc_property_t 類型 的 指針數(shù)組 ,該數(shù)組描述 類聲明的屬性 。
不包括超類 聲明的任何屬性。數(shù)組包含*outCount指針,后跟NULL結(jié)束符。
必須使用free()釋放數(shù)組。
*
*如果cls聲明沒有屬性,或者cls為Nil,則返回NULL和*outCount為0。
*/
// 獲取類聲明 的 屬性列表,獲取到的只有 @property聲明的變量
objc_property_t * class_copyPropertyList ( Class cls,
unsigned int *outCount );
/**
*向具有 給定名稱 和 實(shí)現(xiàn)的類 添加 新方法。
*
* @param cls 要添加方法的類。
* @param name 一個(gè)選擇器,指定要添加的方法的名稱的選擇器。
* @param imp 是新方法的實(shí)現(xiàn)函數(shù),它是新方法的實(shí)現(xiàn)。
該函數(shù)必須至少有兩個(gè)參數(shù) - self和_cmd。
* @param types 是 一個(gè)字符數(shù)組,用于描述方法參數(shù)的類型。
* @return YES如果成功添加方法,否則為NO
*(例如,該類已包含具有該名稱的方法實(shí)現(xiàn))。
* class_addMethod的實(shí)現(xiàn)會(huì)覆蓋父類的方法實(shí)現(xiàn),
*但不會(huì)替換此類中的已存在的實(shí)現(xiàn)。
*要更改現(xiàn)有實(shí)現(xiàn),請(qǐng)使用method_setImplementation。
*/
//向具有給定名稱和實(shí)現(xiàn)的類添加新方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
/**
*返回給定類的指定實(shí)例方法。
*
* @param cls您要操作的類。
* @param name 指定要檢索的方法的選擇器。
* @return 與指定的選擇器的實(shí)現(xiàn) 相對(duì)應(yīng)的方法
* 由 cls指定的類的名稱,或 如果指定的類或其為NULL
*父類不包含具有指定選擇器的實(shí)例方法。
此函數(shù)在父類中搜索實(shí)現(xiàn),而 class_copyMethodList則不在。
*/
// 返回給定類的指定實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
/**
*返回指向描述給定類的給定類方法的數(shù)據(jù)結(jié)構(gòu)的指針。
*
* @param cls指向類定義的指針。 傳遞包含要檢索的方法的類。
* @param name類型為 SEL的指針。 傳遞要檢索的方法的選擇器。
* @return 一個(gè)指向 方法數(shù)據(jù)結(jié)構(gòu)的指針,對(duì)應(yīng)于該實(shí)現(xiàn)
*由aSelector指定的選擇器,用于由aClass指定的類,如果指定,則為NULL
*類或其父類不包含具有指定選擇器的實(shí)例方法。
* 請(qǐng)注意,此函數(shù)在父類中搜索實(shí)現(xiàn),而 class_copyMethodList沒有。
*/
// 獲取類方法,根據(jù)給定類和給定selector獲取Method 的數(shù)據(jù)結(jié)構(gòu)的指針
Method class_getClassMethod ( Class cls, SEL name );
/**
*描述類實(shí)現(xiàn)的實(shí)例方法。
* @param cls您要操作的類。
* @param outCount返回時(shí),包含返回?cái)?shù)組的長(zhǎng)度。
*如果outCount為NULL,則不返回長(zhǎng)度。
*
* @return 描述實(shí)例方法的Method類型的指針數(shù)組
*由類實(shí)現(xiàn) - 不包括父類實(shí)現(xiàn)的任何實(shí)例方法。
*該數(shù)組包含* outCount指針,后跟一個(gè)NULL終止符。 您必須使用free()釋放數(shù)組。
*
*如果cls沒有實(shí)現(xiàn)實(shí)例方法,或者cls為Nil,則返回NULL并且* outCount為0。
*
* 要獲取類的類方法,請(qǐng)使用 class_copyMethodList (object_getClass(cls),&count)。
*
* 獲取可由父類實(shí)現(xiàn)的方法的實(shí)現(xiàn),使用 class_getInstanceMethod
或 class_getClassMethod。
*/
// 獲取所有實(shí)例方法(-號(hào)方法)的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
/**
*替換給定類的方法的實(shí)現(xiàn)。
*
* @param cls 您要修改的類。
* @param name 一個(gè)選擇器,用于標(biāo)識(shí)要 替換其實(shí)現(xiàn)的方法。
* @param imp 由 cls標(biāo)識(shí) 的 類的名稱 標(biāo)識(shí)的方法的新實(shí)現(xiàn)。
* @param types一組字符,用于描述方法參數(shù)的類型。
*由于該函數(shù)必須至少有兩個(gè)參數(shù) - self和_cmd,第二個(gè)和第三個(gè)字符
*必須是“@:”(第一個(gè)字符是返回類型)。
*
* @return 返回 由 cls標(biāo)識(shí)的類的 name標(biāo)識(shí)的方法的先前實(shí)現(xiàn)。
* @note 此函數(shù)以兩種不同的方式運(yùn)行:
* - 如果 name指定的方法尚不存在,則添加它,就像調(diào)用了 class_addMethod一樣。
*由 types指定的類型編碼用于給定。
* - 如果由 name標(biāo)識(shí)的方法確實(shí)存在,則將其 IMP替換為調(diào)用method_setImplementation。
*忽略類型指定的類型編碼。
*/
// 替代方法的實(shí)現(xiàn),替換給定類的方法的實(shí)現(xiàn)。
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
/**
返回一個(gè)函數(shù)指針,如果一個(gè)特定的消息被發(fā)送到一個(gè)類的實(shí)例中,該函數(shù)指針將被調(diào)用。
*
* @param cls 你想要操作的類。
* @param name 一個(gè)選擇器。
*
* @return 一個(gè)函數(shù)指針,如果用類的實(shí)例調(diào)用[object name],
就會(huì)調(diào)用它;如果cls是Nil,就會(huì)調(diào)用NULL。
*
* class_getMethodImplementation可能比method_getImplementation
(class_getInstanceMethod, cls, name)更快。
*
* @注意返回的函數(shù)指針可能是運(yùn)行時(shí)內(nèi)部的函數(shù),而不是一個(gè)實(shí)際的方法實(shí)現(xiàn)。
例如,如果類的實(shí)例不響應(yīng)選擇器,返回的函數(shù)指針將成為運(yùn)行時(shí)消息轉(zhuǎn)發(fā)機(jī)制的一部分。
*/
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
/**
返回一個(gè)函數(shù)指針,如果一個(gè)特定的消息被發(fā)送到一個(gè)類的實(shí)例中,該函數(shù)指針將被調(diào)用。
*
* @param cls 你想要操作的類。
* @param name 一個(gè)選擇器。
* @return 調(diào)用 [object name] 時(shí)將調(diào)用的函數(shù)指針
*帶有類的實(shí)例,或者 NULL(如果 cls是 Nil)。
*/
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類的實(shí)例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
/**
*向類添加屬性。
*
* @param cls 要修改的類。
* @param name 屬性的名稱。
* @param attributes 屬性特性數(shù)組。
* @param attributeCount屬性中的屬性數(shù)。
*
* @return如果已成功添加屬性,則為YES,否則為NO(例如,類已具有該屬性)。
*/
// 為類添加屬性
BOOL class_addProperty ( Class cls, const char *name,
const objc_property_attribute_t *attributes, unsigned int attributeCount );
/**
*替換類的屬性。
*
* @param cls要修改的類。
* @param name屬性的名稱。
* @param attributes 屬性特性數(shù)組。
* @param attributeCount 屬性中的屬性數(shù)。
*/
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name,
const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實(shí)現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實(shí)現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
// 獲取版本號(hào)
int class_getVersion ( Class cls );
// 設(shè)置版本號(hào)
void class_setVersion ( Class cls, int version );
class_getInstanceVariable函數(shù):獲取 類 中指定名稱實(shí)例成員變量的信息。它返回一個(gè)指向包含name指定的成員變量信息的objc_ivar結(jié)構(gòu)體的指針(Ivar)。class_getClassVariable函數(shù):可以獲取類成員變量的信息。目前沒有找到關(guān)于Objective-C中類變量的信息,一般認(rèn)為Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。class_addIvar函數(shù):可以添加成員變量。 Objective-C不支持往已存在的類中添加實(shí)例變量,因此不管是系統(tǒng)庫(kù)提供的類,還是我們自定義的類,都無(wú)法動(dòng)態(tài)添加成員變量。但如果我們通過運(yùn)行時(shí)來(lái)創(chuàng)建一個(gè)類的話,又應(yīng)該如何給它添加成員變量呢?這時(shí)我們就可以使用class_addIvar函數(shù)了。不過需要注意的是,這個(gè)方法只能在objc_allocateClassPair函數(shù)與objc_registerClassPair之間調(diào)用。另外,這個(gè)類也不能是元類。成員變量的按字節(jié)最小對(duì)齊量是1<<alignment。這取決于ivar的類型和機(jī)器的架構(gòu)。如果變量的類型是指針類型,則傳遞log2(sizeof(pointer_type))。class_copyIvarList函數(shù):可以獲取整個(gè)成員變量列表。它返回一個(gè)指向成員變量信息的數(shù)組,數(shù)組中每個(gè)元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個(gè)數(shù)組不包含在父類中聲明的變量。outCount指針返回?cái)?shù)組的大小。需要注意的是,我們必須使用free()來(lái)釋放這個(gè)數(shù)組。class_addMethod函數(shù):向具有給定名稱和實(shí)現(xiàn)的類添加新方法。class_addMethod的實(shí)現(xiàn)會(huì)覆蓋父類的方法實(shí)現(xiàn),但不會(huì)取代本類中已存在的實(shí)現(xiàn),如果本類中包含一個(gè)同名的實(shí)現(xiàn),則函數(shù)會(huì)返回NO。如果要修改已存在實(shí)現(xiàn),可以使用method_setImplementation。一個(gè)Objective-C方法是一個(gè)簡(jiǎn)單的C函數(shù),它至少包含兩個(gè)參數(shù)–self和_cmd。所以,我們的實(shí)現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個(gè)參數(shù),如下所示:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
與成員變量不同的是,我們可以為類動(dòng)態(tài)添加方法,不管這個(gè)類是否已存在。
另外,參數(shù)types是一個(gè)描述傳遞給方法的參數(shù)類型的字符數(shù)組,這就涉及到類型編碼。
class_getInstanceMethod 獲取實(shí)例方法函數(shù)、class_getClassMethod 獲取類方法函數(shù),與class_copyMethodList 獲取所有方法的數(shù)組不同的是,這兩個(gè)函數(shù)都會(huì)去搜索父類的實(shí)現(xiàn)。class_copyMethodList函數(shù):獲取所有方法的數(shù)組。返回包含所有實(shí)例方法的數(shù)組。
如果需要·獲取類方法·,則可以使用class_copyMethodList(object_getClass(cls),&count)(一個(gè)類的實(shí)例方法是定義在元類里面)。該列表不包含父類實(shí)現(xiàn)的方法。outCount參數(shù)返回方法的個(gè)數(shù)。在獲取到列表后,我們需要使用free()方法來(lái)釋放它。-
class_replaceMethod函數(shù):替代方法的實(shí)現(xiàn)。
該函數(shù)的行為可以分為兩種:- 如果類中
不存在name指定的方法,則類似于class_addMethod函數(shù)一樣會(huì)添加方法; - 如果類中
已存在name指定的方法,則類似于method_setImplementation一樣修改已存在實(shí)現(xiàn)來(lái)替代原方法的實(shí)現(xiàn)。
- 如果類中
class_getMethodImplementation函數(shù) :返回方法的具體實(shí)現(xiàn)。該函數(shù)在向類實(shí)例發(fā)送消息時(shí)會(huì)被調(diào)用,并返回一個(gè)指向方法實(shí)現(xiàn)函數(shù)的指針。這個(gè)函數(shù)會(huì)比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數(shù)指針可能是一個(gè)指向runtime內(nèi)部的函數(shù),而不一定是方法的實(shí)際實(shí)現(xiàn)。例如,如果類實(shí)例無(wú)法響應(yīng)selector,則返回的函數(shù)指針將是運(yùn)行時(shí)消息轉(zhuǎn)發(fā)機(jī)制的一部分。class_respondsToSelector函數(shù),我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來(lái)達(dá)到相同目的。class_conformsToProtocol函數(shù):返回 類是否實(shí)現(xiàn)指定的協(xié)議。你通常應(yīng)該使用NSObject類的conformsToProtocol:方法來(lái)替代,而不是這個(gè)函數(shù)。class_copyProtocolList函數(shù):返回 類實(shí)現(xiàn)的協(xié)議列表。在使用后我們需要使用free()手動(dòng)釋放。在MAC OS X系統(tǒng)中**,我們可以使用垃圾回收器。runtime提供了
class_setIvarLayout、class_getIvarLayout、class_setWeakIvarLayout、class_getWeakIvarLayout、幾個(gè)函數(shù)來(lái)確定一個(gè)對(duì)象的內(nèi)存區(qū)域是否可以被垃圾回收器掃描,以處理strong/weak引用。但通常情況下,我們不需要去主動(dòng)調(diào)用這些方法;在調(diào)用objc_registerClassPair時(shí),會(huì)生成合理的布局。在此不詳細(xì)介紹這些函數(shù)3.2.1.2 獲取類定義
Objective-C動(dòng)態(tài)運(yùn)行庫(kù)會(huì)自動(dòng)注冊(cè)我們代碼中定義的所有的類。我們也可以在運(yùn)行時(shí)創(chuàng)建類定義并使用objc_addClass函數(shù)來(lái)注冊(cè)它們。runtime提供了一系列函數(shù)來(lái)獲取類定義相關(guān)的信息,這些函數(shù)主要包括:
// 獲取已注冊(cè)的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );
// 創(chuàng)建并返回一個(gè)指向所有已注冊(cè)類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
/**
*返回指定類的類定義。
*
* @param name 要查找的類的名稱。
* @return 指定類的Class對(duì)象,如果該類未在Objective-C運(yùn)行時(shí)注冊(cè),
則為nil。
* @note: objc_getClass 與此函數(shù)的不同之處在于:
1.如果未注冊(cè)類,則 objc_getClass 調(diào)用類處理程序回調(diào),
2.然后再次檢查以查看該類是否已注冊(cè)。
3. 此函數(shù)不會(huì)調(diào)用類處理程序回調(diào)。
*/
// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
/**
*返回指定類的類定義。
*
* @param name要查找的類的名稱。
* @return指定類的Class對(duì)象。
* @note :此函數(shù)與 objc_getClass相同,但如果找不到類,則會(huì)終止進(jìn)程。
* @note ZeroLink使用此函數(shù),如果沒有找到類,
則會(huì)出現(xiàn)沒有ZeroLink的編譯時(shí)鏈接錯(cuò)誤。
*/
Class objc_getRequiredClass ( const char *name );
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );
獲取類定義的方法有三個(gè):
objc_lookUpClass,objc_getClass和objc_getRequiredClass。如果類在運(yùn)行時(shí)未注冊(cè),則objc_lookUpClass會(huì)返回nil,而objc_getClass會(huì)調(diào)用類處理回調(diào),并再次確認(rèn)類是否注冊(cè),如果確認(rèn)未注冊(cè),再返回nil。而objc_getRequiredClass函數(shù)的操作與objc_getClass相同,只不過如果沒有找到類,則會(huì)殺死進(jìn)程。objc_getMetaClass函數(shù):如果指定的類沒有注冊(cè),則該函數(shù)會(huì)調(diào)用類處理回調(diào),并再次確認(rèn)類是否注冊(cè),如果確認(rèn)未注冊(cè),再返回nil。不過,每個(gè)類定義都必須有一個(gè)有效的元類定義,所以這個(gè)函數(shù)總是會(huì)返回一個(gè)元類定義,不管它是否有效。objc_getClassList函數(shù):獲取已注冊(cè)的類定義的列表。我們不能假設(shè)從該函數(shù)中獲取的類對(duì)象是繼承自NSObject體系的,所以在這些類上調(diào)用方法是,都應(yīng)該先檢測(cè)一下這個(gè)方法是否在這個(gè)類中實(shí)現(xiàn)。
-
3.2.1.3成員變量(ivars)及屬性 -
3.2.1.4方法(methodLists) -
3.2.1.5協(xié)議(objc_protocol_list) -
3.2.1.6. 其它
由于篇幅太長(zhǎng),不能寫在一篇文章中,具體詳情參考:Objective-C Runtime 運(yùn)行時(shí)之二:成員變量、屬性和方法
- 3.2.2. 動(dòng)態(tài)創(chuàng)建類和對(duì)象
runtime的強(qiáng)大之處在于它能在運(yùn)行時(shí)創(chuàng)建類和對(duì)象。
我們來(lái)如何在運(yùn)行
時(shí)動(dòng)態(tài)創(chuàng)建類。下面這個(gè)函數(shù)就是應(yīng)用前面講到的Class,MetaClass的概念,在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建一個(gè)類。這個(gè)函數(shù)來(lái)自《Inside Mac OS X-The Objective-C Programming Language》。
- 1>.
動(dòng)態(tài)創(chuàng)建類
動(dòng)態(tài)創(chuàng)建類 涉及到以下幾個(gè)函數(shù):
/**
*創(chuàng)建一個(gè)新類和元類。
*
* @param superclass 用作新類的父類的類,或者 Nil用于創(chuàng)建新的根類。
* @param name 用作新類名的字符串。該字符串將被復(fù)制。
* @param extraBytes 在類和元類對(duì)象的末尾為索引的ivars分配的字節(jié)數(shù)。通常應(yīng)該是 0。
*
* @return 新類,如果無(wú)法創(chuàng)建類,則為Nil(例如,所需的名稱已在使用中)。
*
* @note 您可以通過調(diào)用 object_getClass(newClass)來(lái)獲取指向新元類的指針。
* @note要?jiǎng)?chuàng)建一個(gè)新類,請(qǐng)先調(diào)用 objc_allocateClassPair。
*然后使用 class_addMethod和 class_addIvar等函數(shù)設(shè)置類的屬性。
*完成構(gòu)建類后,請(qǐng)調(diào)用 objc_registerClassPair。新類現(xiàn)在可以使用了。
* @note應(yīng)將實(shí)例方法和實(shí)例變量添加到類本身。
*類方法應(yīng)添加到元類中。
*/
// 創(chuàng)建一個(gè)新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name,
size_t extraBytes );
// 銷毀一個(gè)類及其相關(guān)聯(lián)的類
void objc_disposeClassPair ( Class cls );
// 在應(yīng)用中注冊(cè)由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls );
為了創(chuàng)建一個(gè)新類,我們需要調(diào)用objc_allocateClassPair。然后使用諸如class_addMethod,class_addIvar等函數(shù)來(lái)為新創(chuàng)建的類添加方法、實(shí)例變量和屬性等。完成這些后,我們需要調(diào)用objc_registerClassPair函數(shù)來(lái)注冊(cè)類,之后這個(gè)新類就可以在程序中使用了。
實(shí)例方法和實(shí)例變量應(yīng)該添加到類自身上,而類方法應(yīng)該添加到類的元類上。
-
objc_disposeClassPair函數(shù):用于銷毀一個(gè)類。不過需要注意的是,如果程序運(yùn)行中還存在類或其子類的實(shí)例,則不能調(diào)用針對(duì)類調(diào)用該方法。
在前面介紹元類時(shí),我們已經(jīng)有接觸到這幾個(gè)函數(shù)了,在此我們?cè)倥e個(gè)實(shí)例來(lái)看看這幾個(gè)函數(shù)的使用。
實(shí)例(Example)
//-----------------------------------------------------------
@interface MyClass : NSObject <NSCopying, NSCoding>
- (void)beReplacedMethod;
@end
//-----------------------------------------------------------
// MyClass.m
#import "MyClass.h"
@interface MyClass ()
@end
@implementation MyClass
- (void)beReplacedMethod {
NSLog(@"Adds an IMP to your new method in a new class");
}
@end
//-----------------------------------------------------------
- (void)viewDidLoad {
MyClass *myClass = [[MyClass alloc] init];
Class cls = myClass.class;
// 類名
NSLog(@"class name: %s", class_getName(cls));
NSLog(@"=====================================");
// 父類
NSLog(@"The name of the superclass of MyClass is: %s", class_getName(class_getSuperclass(cls)));
NSLog(@"=====================================");
// 是否是元類
NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
NSLog(@"======================================");
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
NSLog(@"======================================");
// 變量實(shí)例大小
NSLog(@"instance size: %zu", class_getInstanceSize(cls));
NSLog(@"======================================");
// 創(chuàng)建一個(gè)新類和元類 MyClass.class為新類MySubClass的父類
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
/*
您必須使用Objective-C運(yùn)行時(shí)系統(tǒng)注冊(cè)方法名稱才能
獲得方法的選擇器,然后才能將方法添加到類定義中。
如果方法名稱已經(jīng)注冊(cè),此函數(shù)只返回選擇器。
*/
SEL sel = sel_registerName ("beReplacedMethod");
// class_getMethodImplementation 返回方法的具體實(shí)現(xiàn)
//獲取 MyClass 類中 beReplacedMethod 方法選擇器的 方法實(shí)現(xiàn)地址IMP
IMP imp = class_getMethodImplementation([MyClass class],sel );
//給新創(chuàng)建的 MySubClass 類 添加名稱為 findInSelf的新方法,imp 是新方法的方法實(shí)現(xiàn)地址
class_addMethod(cls, @selector(findInSelf),imp , "v@:");
/**
* @note: 只能在 objc_allocateClassPair 之后和 objc_registerClassPair
之前調(diào)用此函數(shù)。不支持 將實(shí)例變量添加到 現(xiàn)有類。
* @note : 該類 不能是元類 。 不支持將實(shí)例變量添加到元類。
*/
// 添加實(shí)例變量
class_addIvar(cls, "_ivar1", sizeof(NSString *),
log(sizeof(NSString *)), "i");
//屬性的特性(attribute)
objc_property_attribute_t type = {"T", "@\"NSString\""};
//屬性的特性(attribute)
objc_property_attribute_t ownership = { "C", "" };
//屬性的特性(attribute)
objc_property_attribute_t backingivar = { "V", "_ivar1"};
//屬性的特性數(shù)組
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
// 為類添加屬性
class_addProperty(cls, "property2", attrs, 3);
// 在應(yīng)用中注冊(cè)由objc_allocateClassPair創(chuàng)建的類
objc_registerClassPair(cls);
//-----------------------------------------------------------
//開始調(diào)用
id instance = [[cls alloc] init];
//在這里使用 performSelector 來(lái)向新類的對(duì)象發(fā)送消息,
//可以避免編譯警告信息(因?yàn)槲覀儾]有聲明該類及其可響應(yīng)的消息)
[instance performSelector:@selector(findInSelf)];
}
程序的輸出如下:
class name: MyClass
The name of the superclass of MyClass is: NSObject
MyClass is not a meta-class
MyClass's meta-class is MyClass
instance size: 48
Adds an IMP to your new method in a new class
- 2>.動(dòng)態(tài)創(chuàng)建對(duì)象
動(dòng)態(tài)創(chuàng)建對(duì)象的函數(shù)如下:
// 創(chuàng)建類實(shí)例,為該類中的類分配內(nèi)存,默認(rèn)的malloc內(nèi)存區(qū)域
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置創(chuàng)建類實(shí)例
id objc_constructInstance ( Class cls, void *bytes );
// 銷毀類實(shí)例
void * objc_destructInstance ( id obj );
-
class_createInstance函數(shù):創(chuàng)建實(shí)例時(shí),會(huì)在默認(rèn)的內(nèi)存區(qū)域為類分配內(nèi)存,調(diào)用class_createInstance的效果與+alloc方法類似。extraBytes參數(shù)表示分配的額外字節(jié)數(shù)。這些額外的字節(jié)可用于存儲(chǔ)在類定義中所定義的實(shí)例變量之外的實(shí)例變量。該函數(shù)在ARC環(huán)境下無(wú)法使用。
實(shí)例操作函數(shù)
實(shí)例操作函數(shù)主要是針對(duì)我們創(chuàng)建的實(shí)例對(duì)象的一系列操作函數(shù),我們可以使用這組函數(shù)來(lái)從實(shí)例對(duì)象中獲取我們想要的一些信息,如實(shí)例對(duì)象中變量的值。
// 返回指定對(duì)象的一份拷貝
id object_copy ( id obj, size_t size );
// 釋放指定對(duì)象占用的內(nèi)存
id object_dispose ( id obj );
/**
*更改類實(shí)例的實(shí)例變量的值。
*
* @param obj 指向類實(shí)例的指針。 傳遞包含要修改其值的實(shí)例變量的對(duì)象。
* @param name :一個(gè)C字符串。 傳遞要修改其值的實(shí)例變量的名稱。
* @param value: 實(shí)例變量的新值。
* @return : 指向Ivar數(shù)據(jù)結(jié)構(gòu)的指針,該結(jié)構(gòu)定義由name指定的實(shí)例變量的類型和名稱。
* @note: 具有已知內(nèi)存管理的實(shí)例變量(例如ARC強(qiáng)和弱)使用該內(nèi)存管理。
分配具有未知內(nèi)存管理的實(shí)例變量,就好像它們是unsafe_unretained一樣。
*/
// 修改類實(shí)例的實(shí)例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對(duì)象實(shí)例變量的值, outValue: 返回時(shí)包含指向?qū)嵗兞恐档闹羔槨?Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對(duì)象分配的任何額外字節(jié)的指針
void * object_getIndexedIvars ( id obj );
/**
*讀取對(duì)象中實(shí)例變量的值。
*
* @param obj包含要讀取其值的實(shí)例變量的對(duì)象。
* @param ivar Ivar描述要讀取其值的實(shí)例變量。
* @return : 由ivar指定的實(shí)例變量的值,如果object為nil,則為nil。
* @note:如果已知實(shí)例變量的Ivar,則object_getIvar比object_getInstanceVariable快。
*/
//如果使用ARC,則在獲取`不是對(duì)象`的`返回值`時(shí),執(zhí)行項(xiàng)目會(huì)導(dǎo)致項(xiàng)目崩潰。但是如果`自己創(chuàng)建的類`,然后`自己添加的屬性`,就可以用這個(gè)函數(shù)獲取返回值(不管什么類型)
// 返回對(duì)象中實(shí)例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設(shè)置對(duì)象中實(shí)例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
// 返回給定對(duì)象的類名
const char * object_getClassName ( id obj );
// 返回對(duì)象的類
Class object_getClass ( id obj );
/**
*設(shè)置對(duì)象的類。
*
* @param obj要修改的對(duì)象。
* @param cls一個(gè)類對(duì)象。
* @return對(duì)象類的先前值,或如果對(duì)象為nil,則為Nil。
*/
// 設(shè)置對(duì)象的類
Class object_setClass(id obj, Class cls);
- 如果實(shí)例變量的
Ivar已經(jīng)知道,那么調(diào)用object_getIvar會(huì)比object_getInstanceVariable函數(shù)快,相同情況下,object_setIvar也比object_setInstanceVariable快。
常見面試題
1.runtime 怎么添加成員變量,屬性,方法等?
2.能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?
參考答案:(自己總結(jié)的,若有誤或更好的答案,請(qǐng)留言)

- 不能向編譯后得到的類中增加實(shí)例變量;
- 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量;
- 分析如下:
- 因?yàn)?code>編譯后的類
已經(jīng)注冊(cè)在runtime中,類結(jié)構(gòu)體中的objc_ivar_list實(shí)例變量的鏈表和instance_size實(shí)例變量的內(nèi)存大小已經(jīng)確定,同時(shí)runtime 會(huì)調(diào)用class_setIvarLayout 或 class_setWeakIvarLayout來(lái)處理strong weak引用,所以不能向存在的類中添加實(shí)例變量 -
運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù),但是得在調(diào)用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
- 因?yàn)?code>編譯后的類
所謂靜態(tài)語(yǔ)言,就是在程序運(yùn)行前決定了所有的類型判斷,類的所有成員、方法在編譯階段就確定好了內(nèi)存地址。也就意味著所有類對(duì)象只能訪問屬于自己的成員變量和方法,否則編譯器直接報(bào)錯(cuò)。
而
動(dòng)態(tài)語(yǔ)言,恰恰相反,類型的判斷、類的成員變量、方法的內(nèi)存地址都是在程序的運(yùn)行階段才最終確定,并且還能動(dòng)態(tài)的添加成員變量和方法。也就意味著你調(diào)用一個(gè)不存在的方法時(shí),編譯也能通過,甚至一個(gè)對(duì)象它是什么類型并不是表面我們所看到的那樣,只有運(yùn)行之后才能決定其真正的類型。相比于靜態(tài)語(yǔ)言,動(dòng)態(tài)語(yǔ)言具有較高的靈活性和可訂閱性。而oc,正是一門動(dòng)態(tài)語(yǔ)言。


小結(jié)
在這一章中我們介紹了Runtime運(yùn)行時(shí)中與類和對(duì)象相關(guān)的數(shù)據(jù)結(jié)構(gòu),通過這些數(shù)據(jù)函數(shù),我們可以管窺Objective-C底層面向?qū)ο髮?shí)現(xiàn)的一些信息。另外,通過豐富的操作函數(shù),可以靈活地對(duì)這些數(shù)據(jù)進(jìn)行操作。
參考資料:
Objective-C Runtime Programming Guide
Objective-CRuntime Reference
http://southpeak.github.io/2014/10/25/objective-c-runtime-1/