第八更 runtime總結

一、什么是runtime
C++編寫的程序通過編譯器直接把函數(shù)地址硬編碼進入可執(zhí)行文件;而Objective-C無法通過編譯器直接把函數(shù)地址硬編碼進入可執(zhí)行文件,而是在程序運行的時候,利用Runtime根據(jù)條件判斷作出決定。函數(shù)標識與函數(shù)過程的真正內(nèi)容之間的關聯(lián)可以動態(tài)修改。Runtime是Objective不可缺少的重要一部分。

二、Objective-C的元素認知

2.1打開/Public Headers/objc.h文件可以看到如下定義:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

Class是一個指向objc_class結構體的指針,而id是一個指向objc_object結構體的指針,其中的isa是一個指向objc_class結構體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。

打開/Public Headers/runtime.h文件
objc_class的定義如下:

typedef struct objc_class *Class;
struct objc_class { 
 Class isa                                 OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
 Class super_class                         OBJC2_UNAVAILABLE; // 父類
 const char *name                          OBJC2_UNAVAILABLE; // 類名
 long version                              OBJC2_UNAVAILABLE; // 類的版本信息,默認為0,可以通過runtime函數(shù)class_setVersion或者class_getVersion進行修改、讀取
 long info                                 OBJC2_UNAVAILABLE; // 類信息,供運行時期使用的一些位標識,如CLS_CLASS (0x1L) 表示該類為普通 class,其中包含實例方法和變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
 long instance_size                        OBJC2_UNAVAILABLE; // 該類的實例變量大?。ò◤母割惱^承下來的實例變量)
 struct objc_ivar_list *ivars              OBJC2_UNAVAILABLE; // 該類的成員變量地址列表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法地址列表,與 info 的一些標志位有關,如CLS_CLASS (0x1L),則存儲實例方法,如CLS_META (0x2L),則存儲類方法;
 struct objc_cache *cache                  OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址,用于提升效率;
 struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE; // 存儲該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

由以上代碼可見,類與對象的區(qū)別就是類比對象多了很多特征成員,類也可以當做一個objc_object來對待,也就是說類和對象都是對象,分別稱作類對象(class object)和實例對象(instance object),這樣我們就可以區(qū)別對象和類了。

isa:objc_object(實例對象)中isa指針指向的類結構稱為class(也就是該對象所屬的類)其中存放著普通成員變量與動態(tài)方法(“-”開頭的方法);此處isa指針指向的類結構稱為metaclass,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)。

super_class: 指向該類的父類的指針,如果該類是根類(如NSObject或NSProxy),那么super_class就為nil。

2.2 SEL
SEL是selector在Objective-C中的表示類型。selector可以理解為區(qū)別方法的ID。

typedef struct objc_selector *SEL;

objc_selector的定義如下:

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;// 名稱
    char *types;                      OBJC2_UNAVAILABLE;// 類型
};

name和types都是char類型。

2.3 IMP

終于到IMP了,它在objc.h中得定義如下:

typedef id (*IMP)(id, SEL, ...);

IMP是“implementation”的縮寫,它是由編譯器生成的一個函數(shù)指針。當你發(fā)起一個消息后(下文介紹),這個函數(shù)指針決定了最終執(zhí)行哪段代碼

2.4 METHOD

Method代表類中的某個方法的類型。

typedef struct objc_method *Method;

objc_method的定義如下:

struct objc_method {
    SEL method_name                   OBJC2_UNAVAILABLE; // 方法名
    char *method_types                OBJC2_UNAVAILABLE; // 方法類型
    IMP method_imp                    OBJC2_UNAVAILABLE; // 方法實現(xiàn)
}

方法名method_name類型為SEL,上文提到過。
方法類型method_types是一個char指針,存儲著方法的參數(shù)類型和返回值類型。
方法實現(xiàn)method_imp的類型為IMP,上文提到過。

2.5 Ivar

Ivar代表類中實例變量的類型

typedef struct objc_ivar *Ivar;

objc_ivar的定義如下:

struct objc_ivar {
    char *ivar_name                   OBJC2_UNAVAILABLE; // 變量名
    char *ivar_type                   OBJC2_UNAVAILABLE; // 變量類型
    int ivar_offset                   OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE; // 占用空間
#endif
}

2.6 objc_property_t
objc_property_t是屬性,它的定義如下:

typedef struct objc_property *objc_property_t;

objc_property是內(nèi)置的類型,與之關聯(lián)的還有一個objc_property_attribute_t,它是屬性的attribute,也就是其實是對屬性的詳細描述,包括屬性名稱、屬性編碼類型、原子類型/非原子類型等。它的定義如下:

typedef struct {
    const char *name; // 名稱
    const char *value;  // 值(通常是空的)
} objc_property_attribute_t;

2.7 Cache
Catch的定義如下:

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache {
    unsigned int mask                   OBJC2_UNAVAILABLE;
    unsigned int occupied               OBJC2_UNAVAILABLE;
    Method buckets[1]                   OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的總數(shù)。在方法查找中,Runtime使用這個字段確定數(shù)組的索引位置。
occupied: 實際占用cache buckets的總數(shù)。
buckets: 指定Method數(shù)據(jù)結構指針的數(shù)組。這個數(shù)組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個數(shù)組可能會隨著時間而增長。
objc_msgSend(下文講解)每調(diào)用一次方法后,就會把該方法緩存到cache列表中,下次的時候,就直接優(yōu)先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。

2.8 Catagory

這個就是我們平時所說的類別了,很熟悉吧。它可以動態(tài)的為已存在的類添加新的方法。
它的定義如下:

typedef struct objc_category *Category;

objc_category的定義如下:

struct objc_category {
    char *category_name                           OBJC2_UNAVAILABLE; // 類別名稱
    char *class_name                              OBJC2_UNAVAILABLE; // 類名
    struct objc_method_list *instance_methods     OBJC2_UNAVAILABLE; // 實例方法列表
    struct objc_method_list *class_methods        OBJC2_UNAVAILABLE; // 類方法列表
    struct objc_protocol_list *protocols          OBJC2_UNAVAILABLE; // 協(xié)議列表
}

因為是入門,以上就列舉這些吧!

三、Objective-C的消息傳遞

3.1 基本消息傳遞

在面向?qū)ο缶幊讨?,對象調(diào)用方法叫做發(fā)送消息。在編譯時,程序的源代碼就會從對象發(fā)送消息轉(zhuǎn)換成Runtime的objc_msgSend函數(shù)調(diào)用。
例如某實例變量receiver實現(xiàn)某一個方法oneMethod

[receiver oneMethod];

Runtime會將其轉(zhuǎn)成類似這樣的代碼

objc_msgSend(receiver, selector);

具體會轉(zhuǎn)換成什么代碼呢?
Runtime會根據(jù)類型自動轉(zhuǎn)換成下列某一個函數(shù):
objc_msgSend:普通的消息都會通過該函數(shù)發(fā)送
objc_msgSend_stret:消息中有數(shù)據(jù)結構作為返回值(不是簡單值)時,通過此函數(shù)發(fā)送和接收返回值
objc_msgSendSuper:和objc_msgSend類似,這里把消息發(fā)送給父類的實例
objc_msgSendSuper_stret:和objc_msgSend_stret類似,這里把消息發(fā)送給父類的實例并接收返回值
當消息被發(fā)送到實例對象時,是如圖所示處理的(圖片源自網(wǎng)絡):

objective-runtime-2.png

objc_msgSend函數(shù)的調(diào)用過程:

  • 第一步:檢測這個selector是不是要忽略的。
  • 第二步:檢測這個target是不是nil對象。nil對象發(fā)送任何一個消息都會被忽略掉。
  • 第三步:
    1.調(diào)用實例方法時,它會首先在自身isa指針指向的類(class)methodLists中查找該方法,如果找不到則會通過class的super_class指針找到父類的類對象結構體,然后從methodLists中查找該方法,如果仍然找不到,則繼續(xù)通過super_class向上一級父類結構體中查找,直至根class;
    2.當我們調(diào)用某個某個類方法時,它會首先通過自己的isa指針找到metaclass,并從其中methodLists中查找該類方法,如果找不到則會通過metaclass的super_class指針找到父類的metaclass對象結構體,然后從methodLists中查找該方法,如果仍然找不到,則繼續(xù)通過super_class向上一級父類結構體中查找,直至根metaclass;
  • 第四部:前三部都找不到就會進入動態(tài)方法解析(看下文)。**

四、動態(tài)解析
動態(tài)解析流程圖(圖片來自網(wǎng)絡):


objective-runtime-6.png

第一步:通過resolveInstanceMethod:或者resolveClassMethod方法決定是否動態(tài)添加方法。如果返回Yes則通過class_addMethod動態(tài)添加方法,消息得到處理,結束;如果返回No,則進入下一步;
第二步:這步會進入forwardingTargetForSelector:方法,用于指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調(diào)用對象的方法,結束。如果返回nil,則進入第三部;
第三部:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步;
第四部:這步調(diào)用forwardInvocation:方法,我們可以通過anInvocation對象做很多處理,比如修改實現(xiàn)方法,修改響應對象等,如果方法調(diào)用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法,若我們沒有實現(xiàn)這個方法,那么就會crash。

demo傳送門:https://www.ianisme.com/ios/2019.html

五、runtime項目應用

5.1、實例變量遍例

遍例類的所有實例變量,實現(xiàn)自動歸檔-反歸檔
Ivar *ivars = class_copyIvarList([self class], &count);

//    KVC中setValue中使用
//    我們知道在KVC中如果直接setValue如果對象沒有這個屬性或者是變量就會直接Crash,如:
    SomeObj *obj = [[SomeObj alloc]init];
    [obj setValue:@"value" forKey:@"objName"];
//    SomeObj 沒有objName這個屬性
  //    這段代碼會直接Crash,使用runtime遍例實例變量避免

5.2、動態(tài)關聯(lián)對象

使用Runtime來在一個已有對象上動態(tài)的掛載另一個對象
如:如果你在對象傳遞(傳參)的時候需要用到某個屬性,按照以往的思路:我繼承這個類重新創(chuàng)建一個新類就完事了,這個思路沒有問題,但麻煩,要是有一個方法能直接將我想要的屬性掛載上去豈不是更好?代碼簡單、易懂。
實際開發(fā)中,一個UIViewController中多個UITableView(或UIAlertView)它們的代理相同,動態(tài)關聯(lián)后在代理方法中就可以分別不同對象進行不同處理。
下面就來講解下如何使用Runtime來 在已有對象上動態(tài)掛載另外一個對象。

5.3、動態(tài)關聯(lián)函數(shù)/方法
動態(tài)關聯(lián)方法,在運時期動態(tài)增加、交換方法。
替換系統(tǒng)方法,自動實現(xiàn)功能。
遍例系統(tǒng)類的方法,查看新特性。

demo實例:http://www.itdecent.cn/p/902749ed3e4c

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

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

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