Objective-C Runtime 運(yùn)行時(shí)之一: 類與對(duì)象及其相關(guān)面試題

目錄:

1. 什么是Runtime?
2. 與 Runtime 系統(tǒng)進(jìn)行交互
3. Runtime 類與對(duì)象
    1. 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. 獲取類定義
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)交互:

  1. 通過Objective-C源代碼
  2. 通過類NSObject的方法
  3. 通過運(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)行一定程度的自我檢查。例如:

  1. -class方法 : 返回對(duì)象的類;
  2. -isKindOfClass:-isMemberOfClass:方法 : 檢查對(duì)象是否存在于指定的類的繼承體系中(也可以說(shuō)成是判斷 是否是其子類或者父類或者當(dāng)前類的成員變量);
  3. -respondsToSelector: : 檢查對(duì)象能否響應(yīng)指定的消息;
  4. -conformsToProtocol: : 檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;
  5. -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_CLASSobjc_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-classisa指向基類(·根類·)的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類(·根類·)的meta-classisa指針指向它自己。這樣就形成了一個(gè)完美的閉環(huán)。

通過上面的描述,再加上對(duì)objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了。子類父類,根類(這些都是普通 class)以及其對(duì)應(yīng)metaclassisasuper_class 之間關(guān)系 如下圖所示

instance object,class,metaclass 的 isa 與 super_class 關(guān)系圖
  • 規(guī)則一:類的實(shí)例對(duì)象的 isa 指向該類;該類的 isa 指向該類的 metaclass;
    理解Classobjc_objectisa 指向該 Class 類,該類(objc_class)的isa指向Class metaClass`)
  • 規(guī)則二:類的 super_class指向其父類,如果該類為根類則值為NULL;
    理解Classsuper_class 指向該Class父類,如果該類Class最頂層的類,那么該Classsuper_class 值為NULL)
  • 規(guī)則三:metaclassisa 指向根 metaclass,如果該 metaclass根 metaclass指向自身
    理解不管是子類還是父類metaclassisa 一律 都指向 根 metaclass,如果該 metaclass 是根 metaclass指向自身)
  • 規(guī)則四:metaclasssuper_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ū)別呢?

classinstance 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];

metaclassclass 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ì)在 NSStringmetaclass 類結(jié)構(gòu)的 methodlists 中去查找 stringWithString 的響應(yīng)函數(shù)。

[NSString stringWithString:@"str"];  //stringWithString 是 + 號(hào)方法

好,至此我們明白了類的結(jié)構(gòu)層次,來(lái)看張圖會(huì)更明顯:

類存儲(chǔ)示意圖

總結(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_setWeakIvarLayoutclass_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_getClassobjc_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)。

由于篇幅太長(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之前,原因同上。

所謂靜態(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容