深入理解 iOS Runtime

了解Runtime有助于我們理解Objective-C運(yùn)行時系統(tǒng)的工作原理以及如何利用它。本章將介紹NSObject類以及Objective-C程序如何與運(yùn)行時系統(tǒng)進(jìn)行交互,如何在運(yùn)行時查找對象的信息,如何將消息轉(zhuǎn)發(fā)給其他對象。

Runtime簡介

Runtime 又稱運(yùn)行時,是iOS系統(tǒng)的核心,它是一套底層的C語言API。它會將一些工作放在代碼運(yùn)行時才處理而并非編譯時,所以有很多類和成員變量在我們編譯時是不知道的,而在運(yùn)行時,我們所編寫的代碼會轉(zhuǎn)換成完整的確定的代碼運(yùn)行。

運(yùn)行時交互

Objective-C中與運(yùn)行時系統(tǒng)有三個層次的交互:

  • 通過Objective-C源碼
  • 通過Foundation中的NSObject定義的方法
  • 通過直接調(diào)用運(yùn)行時函數(shù)

Objective-C 源代碼

顧名思義,只要編寫和編譯 Objective-C代碼即可使用它。編譯包含Objective-C 類和方法的代碼時,編譯器會創(chuàng)建實(shí)現(xiàn)語言動態(tài)特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)。

NSObject 方法

Cocoa 中,大多數(shù)對象都是NSObject的子類,因此都繼承了他定義的方法。(NSProxy是個特例)
NSObject中有一些方法可以簡單地向運(yùn)行時系統(tǒng)查詢信息。支持對象執(zhí)行自省。例如class方法,有isKindOfClass:isMemberOfClass:, 檢查對象在繼承層級結(jié)構(gòu)的位置是否正確;
conformsToProtocol:,對象是否要實(shí)現(xiàn)特定協(xié)議中定義的方法;respondsToSelector:,表示對象是否可以接受特定消息;

// 檢查對象在繼承層級結(jié)構(gòu)的位置;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;

// 指示對象是否聲稱要實(shí)現(xiàn)特定協(xié)議中定義的方法
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

// 表示對象是否可以接受特定消息 (檢查是否實(shí)現(xiàn)某個方法)
- (BOOL)respondsToSelector:(SEL)aSelector;

// 方法實(shí)現(xiàn)的地址
- (IMP)methodForSelector:(SEL)aSelector;

運(yùn)行時函數(shù)

運(yùn)行時系統(tǒng)是一個共享的動態(tài)庫,公共的接口在目錄下/usr/include/objc。其中包含了一些函數(shù)和數(shù)據(jù)結(jié)構(gòu),許多函數(shù)可以使用純C來復(fù)制編譯器在編寫 Objective-C 代碼時所做的事情。這其實(shí)也就是我們常常看到的,使用某些運(yùn)行時函數(shù)可以達(dá)到可以NSObject方法一樣的效果,其實(shí)也正是這些底層的函數(shù)構(gòu)成了NSObject的基礎(chǔ)功能。這里是官方文檔Objective-C 運(yùn)行時參考

現(xiàn)在,我們知道了運(yùn)行時交互有哪些,那么接下來,我們再看看Objective-C中的一些基本概念:類、對象、Method、SEL、IMP。熟悉這些概念之后,會更加理解運(yùn)行時做了哪些事。

類、對象、Method、SEL、IMP

類對象(Class)是由程序設(shè)置后在運(yùn)行時由編譯器創(chuàng)建的,當(dāng)一個對象的實(shí)例方法被調(diào)用時,會通過isa找到這個類,然后在該類中方法列表中查找。

// Class 定義
typedef struct objc_class *Class;
// 類結(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;

結(jié)構(gòu)體里有指向父類的指針、類名、版本、實(shí)例大小、實(shí)例變量列表、方法列表、緩存、遵守的協(xié)議列表等。

那疑問來了,請問類方法是存在哪里的?我們在調(diào)用類方法的時候,我們?nèi)绾稳フ夷兀窟@里就引入一個概念:元類(meta-class)。(想要深入了解元類可以查看這篇文章 What is a meta-class in Objective-C?)

元類就是類對象的isa指向的類,也可以說是類對象的類

對象

實(shí)例對象(Object)是我們對類對象alloc或者new操作時所創(chuàng)建的,在這個過程中會拷貝實(shí)例所屬的類的成員變量,但并不拷貝類定義的方法。調(diào)用實(shí)例方法時,系統(tǒng)會根據(jù)實(shí)例的isa指針去類的方法列表及父類的方法列表中尋找與消息對應(yīng)的selector指向的方法

// 對象結(jié)構(gòu)體
struct objc_object {
    Class _Nonnull isa;     OBJC_ISA_AVAILABILITY;
};

由此,我們得出了一個結(jié)論,類對象和實(shí)例對象的查找機(jī)制是一樣的:

  • 對象的實(shí)例方法調(diào)用時,通過對象的 isa 在類中獲取方法的實(shí)現(xiàn)。
  • 類對象的類方法調(diào)用時,通過類的 isa 在元類中獲取方法的實(shí)現(xiàn)。

對應(yīng)關(guān)系如下圖,描述了對象,類,元類之間的關(guān)系:

圖中實(shí)線是 super_class指針,虛線是isa指針。

  • Root class (class)就是NSObject,NSObject沒有超類,所以Root class(class)的superclass指向nil。
  • 每個Class都有一個isa指向唯一的Meta class
  • Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個回路。
  • 每個Meta class的isa都指向Root class (meta)。

Method

Method就是我們平時所說的函數(shù),它表示的是能夠獨(dú)立完成一個功能的一段代碼;Method通過selectorIMP兩個屬性,實(shí)現(xiàn)了快速查詢方法及實(shí)現(xiàn),相對提高了性能,又保持了靈活性。

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp   

SEL

SEL是方法選擇器, 常見的寫法有:@selector()

/// 代表一個方法的不透明類型
typedef struct objc_selector *SEL;
聲明方式:
SEL s1 = @selector(test1);
SEL s2 = NSSelectorFromString(@"test2");

IMP

IMP是指向最終實(shí)現(xiàn)程序的內(nèi)存地址的指針,下面是它的定義:

/// 指向一個方法實(shí)現(xiàn)的指針
typedef id (*IMP)(id, SEL, ...); 
#endif

理解了前面的這些概念之后,接下來我們進(jìn)入正題。

在 Objective-C 中,消息直到運(yùn)行時才綁定到方法實(shí)現(xiàn)。編譯器轉(zhuǎn)換消息表達(dá)式,調(diào)用objc_msgSend方法。

消息發(fā)送

Objective-C 中所有方法的調(diào)用/類的生成都在運(yùn)行時進(jìn)行,我們可以通過類名/方法名反射得到相應(yīng)的類和方法,也可以替換某個類的方法為新的實(shí)現(xiàn),理論上你可以在運(yùn)行時通過類名/方法名調(diào)用到任何 Objective-C 方法,替換任何類的實(shí)現(xiàn)以及新增任意類。

比方說我們寫一個調(diào)用方法[receiver message],那這個方法會被編譯器轉(zhuǎn)化成:

// 第一個參數(shù)類型是發(fā)送者, 第二個參數(shù)類型是SEL。SEL在OC中是selector方法選擇器
id objc_msgSend ( id _Nullable self, SEL op, ... );

不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器。由于這點(diǎn)特性,也導(dǎo)致了Objective-C不支持函數(shù)重載。

實(shí)際上,我們在調(diào)用的方法的過程,其實(shí)在Runtime中就是消息發(fā)送。

objc_msgSend的實(shí)現(xiàn)是由匯編語言實(shí)現(xiàn),根據(jù)CPU架構(gòu)實(shí)現(xiàn)的過程各不相同,如果想閱讀相關(guān)的代碼要有一定的匯編基礎(chǔ);

objc_msgSend會做以下幾件事情:

  • 1.檢測這個 selector是不是要忽略

  • 2.檢查target是不是為nil

    • 如果這里有相應(yīng)的nil的處理函數(shù),就跳轉(zhuǎn)到相應(yīng)的函數(shù)中
    • 如果沒有處理nil的函數(shù),就自動清理并返回。這一點(diǎn)就是為何在Objective-C中給nil發(fā)送消息不會崩潰的原因
  • 3.確定不是給nil發(fā)消息之后,在該class的緩存中查找方法對應(yīng)的IMP實(shí)現(xiàn)

    • 如果找到,就跳轉(zhuǎn)進(jìn)去執(zhí)行
    • 如果沒有找到,就在方法列表里面繼續(xù)查找,一直找到NSObject為止
  • 4.如果還沒有找到,那就需要開始消息轉(zhuǎn)發(fā)階段了。至此,發(fā)送消息Messaging階段完成。這一階段主要完成的是通過select()快速查找IMP的過程

消息傳遞框架:


為了加快消息傳遞過程,運(yùn)行時系統(tǒng)會在使用方法時緩存方法的選擇器和地址。每個類都有一個單獨(dú)的緩存,它可以包含繼承方法以及類中定義的方法的選擇器。在消息傳遞過程中,會首先檢查接收對象類的緩存。

消息轉(zhuǎn)發(fā)

轉(zhuǎn)發(fā)階段,會調(diào)用_objc_msgForward(id self, SEL _cmd,...)方法

_objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...) 

_objc_msgForward會做以下幾件事情:

  • 1.先調(diào)用 forwardingTargetForSelector 方法獲取新的 target 作為 receiver 重新執(zhí)行 selector,
    • 如果返回的內(nèi)容合法, 跳轉(zhuǎn)去執(zhí)行
    • 如果返回的內(nèi)容不合法(為 nil 或者跟舊 receiver 一樣),繼續(xù)執(zhí)行后續(xù)方法。
  • 2.調(diào)用 methodSignatureForSelector獲取方法簽名后,判斷返回類型信息是否正確,再調(diào)用 forwardInvocation 執(zhí)行 NSInvocation 對象,并將結(jié)果返回。如果對象沒實(shí)現(xiàn) methodSignatureForSelector 方法,繼續(xù)執(zhí)行后面方法。
  • 3.調(diào)用 doesNotRecognizeSelector 方法,拋出異常。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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