iOS開發底層知識——Runtime詳解

來源:互聯網
上載者:User
首先,讓我們先對runtime的底層概念梳理下,若想看怎麼用可以翻到底部。 簡介

Runtime 又叫運行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 代碼,底層都是基於它來實現的。比如:

[receiver message];// 底層運行時會被編譯器轉化為:objc_msgSend(receiver, selector)// 如果其還有參數比如:[receiver message:(id)arg...];// 底層運行時會被編譯器轉化為:objc_msgSend(receiver, selector, arg1, arg2, ...)

以上你可能看不出它的價值,但是我們需要瞭解的是 Objective-C 是一門動態語言,它會將一些工作放在代碼運行時才處理而並非編譯時間。也就是說,有很多類和成員變數在我們編譯的時是不知道的,而在運行時,我們所編寫的代碼會轉換成完整的確定的代碼運行。

因此,編譯器是不夠的,我們還需要一個運行時系統(Runtime system)來處理編譯後的代碼。

Runtime 基本是用 C 和彙編寫的,由此可見蘋果為了動態系統的高效而做出的努力。蘋果和 GNU 各自維護一個開源的 Runtime 版本,這兩個版本之間都在努力保持一致。

點擊這裡下載蘋果維護的開原始碼。 Runtime 的作用

Objc 在三種層面上與 Runtime 系統進行互動: 通過 Objective-C 原始碼 通過 Foundation 架構的 NSObject 類定義的方法 通過對 Runtime 庫函數的直接調用 Objective-C 原始碼

多數情況我們只需要編寫 OC 代碼即可,Runtime 系統自動在幕後搞定一切,還記得簡介中如果我們調用方法,編譯器會將 OC 代碼轉換成運行時代碼,在運行時確定資料結構和函數。 通過 Foundation 架構的 NSObject 類定義的方法

Cocoa 程式中絕大部分類都是 NSObject 類的子類,所以都繼承了 NSObject 的行為。(NSProxy 類時個例外,它是個抽象超類)

一些情況下,NSObject 類僅僅定義了完成某件事情的模板,並沒有提供所需要的代碼。例如 -description 方法,該方法返回類內容的字串表示,該方法主要用來偵錯工具。NSObject 類並不知道子類的內容,所以它只是返回類的名字和對象的地址,NSObject 的子類可以重新實現。

還有一些 NSObject 的方法可以從 Runtime 系統中擷取資訊,允許對象進行自我檢查。例如: -class方法返回對象的類; -isKindOfClass: 和 -isMemberOfClass: 方法檢查對象是否存在於指定的類的繼承體系中(是否是其子類或者父類或者當前類的成員變數); -respondsToSelector: 檢查對象能否響應指定的訊息; -conformsToProtocol:檢查對象是否實現了指定協議類的方法; -methodForSelector: 返回指定方法實現的地址。 通過對 Runtime 庫函數的直接調用

Runtime 系統是具有公用介面的動態共用程式庫。標頭檔存放於/usr/include/objc目錄下,這意味著我們使用時只需要引入objc/Runtime.h標頭檔即可。

許多函數可以讓你使用純 C 代碼來實現 Objc 中同樣的功能。除非是寫一些 Objc 與其他語言的橋接或是底層的 debug 工作,你在寫 Objc 代碼時一般不會用到這些 C 語言函數。對於公用介面都有哪些,後面會講到。我將會參考蘋果官方的 API 文檔。 一些 Runtime 的術語的資料結構

要想全面瞭解 Runtime 機制,我們必須先瞭解 Runtime 的一些術語,他們都對應著資料結構。 SEL

它是selector在 Objc 中的表示(Swift 中是 Selector 類)。selector 是方法選取器,其實作用就和名字一樣,日常生活中,我們通過人名辨別誰是誰,注意 Objc 在相同的類中不會有命名相同的兩個方法。selector 對方法名進行封裝,以便找到對應的方法實現。它的資料結構是:

typedef struct objc_selector *SEL;

我們可以看出它是個映射到方法的 C 字串,你可以通過 Objc 編譯器器命令@selector() 或者 Runtime 系統的 sel_registerName 函數來擷取一個 SEL 類型的方法選取器。

注意:
不同類中相同名字的方法所對應的 selector 是相同的,由於變數的類型不同,所以不會導致它們調用方法實現混亂。 id

id 是一個參數類型,它是指向某個類的執行個體的指標。定義如下:

typedef struct objc_object *id;struct objc_object { Class isa; };

以上定義,看到 objc_object 結構體包含一個 isa 指標,根據 isa 指標就可以找到對象所屬的類。

注意:
isa 指標在代碼運行時並不總指向執行個體對象所屬的類型,所以不能依靠它來確定類型,要想確定類型還是需要用對象的 -class 方法。

PS:KVO 的實現機理就是將被觀察對象的 isa 指標指向一個中間類而不是真實類型,詳見:KVO章節。 Class

typedef struct objc_class *Class;

Class 其實是指向 objc_class 結構體的指標。objc_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;

從 objc_class 可以看到,一個運行時類中關聯了它的父類指標、類名、成員變數、方法、緩衝以及附屬的協議。

其中 objc_ivar_list 和 objc_method_list 分別是成員變數列表和方法列表:

// 成員變數列表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;}                                                            OBJC2_UNAVAILABLE;// 方法列表struct objc_method_list {    struct objc_method_list *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;}

由此可見,我們可以動態修改 *methodList 的值來新增成員方法,這也是 Category 實現的原理,同樣解釋了 Category 不能添加屬性的原因。這裡可以參考下美團技術團隊的文章:深入理解 Objective-C: Category。

objc_ivar_list 結構體用來儲存成員變數的列表,而 objc_ivar 則是儲存了單個成員變數的資訊;同理,objc_method_list 結構體儲存著方法數組的列表,而單個方法的資訊則由 objc_method 結構體儲存。

值得注意的時,objc_class 中也有一個 isa 指標,這說明 Objc 類本身也是一個對象。為了處理類和對象的關係,Runtime 庫建立了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的中繼資料。

我們所熟悉的類方法,就源自於 Meta Class。我們可以理解為類方法就是類對象的執行個體方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。

當你發出一個類似 [NSObject alloc](類方法) 的訊息時,實際上,這個訊息被發送給了一個類對象(Class Object),這個類對象必須是一個元類的執行個體,而這個元類同時也是一個根元類(Root Meta Class)的執行個體。所有元類的 isa 指標最終都指向根元類。

所以當 [NSObject alloc] 這條訊息發送給類對象的時候,運行時代碼 objc_msgSend() 會去它元類中尋找能夠響應訊息的方法實現,如果找到了,就會對這個類對象執行方法調用。
 

上圖實現是 super_class 指標,虛線時 isa 指標。而根元類的父類是 NSObject,isa指向了自己。而 NSObject 沒有父類。

最後 objc_class 中還有一個 objc_cache ,緩衝,它的作用很重要,後面會提到。 Method

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

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

objc_method 儲存了方法名,方法類型和方法實現: 方法名類型為 SEL 方法類型 method_types 是個 char 指標,儲存方法的參數類型和傳回值類型 method_imp 指向了方法的實現,本質是一個函數指標 Ivar

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_offset 是基地址位移位元組 IMP

IMP在objc.h中的定義是:

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

它就是一個函數指標,這是由編譯器產生的。當你發起一個 ObjC 訊息之後,最終它會執行的那段代碼,就是由這個函數指標指定的。而 IMP 這個函數指標就指向了這個方法的實現。

如果得到了執行某個執行個體某個方法的入口,我們就可以繞開訊息傳遞階段,直接執行方法,這在後面 Cache 中會提到。

你會發現 IMP 指向的方法與 objc_msgSend 函數類型相同,參數都包含 id 和 SEL 類型。每個方法名都對應一個 SEL 類型的方法選取器,而每個執行個體對象中的 SEL 對應的方法實現肯定是唯一的,通過一組 id和 SEL 參數就能確定唯一的方法實現地址。

而一個確定的方法也只有唯一的一組 id 和 SEL 參數。 Cache

Cache 定義如下:

typedef struct objc_cache *Cachestruct objc_cache {    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;    unsigned int occupied                                    OBJC2_UNAVAILABLE;    Method buckets[1]                                        OBJC2_UNAVAILABLE;};

Cache 為方法調用的效能進行最佳化,每當執行個體對象接收到一個訊息時,它不會直接在 isa 指標指向的類的方法列表中遍曆尋找能夠響應的方法,因為每次都要尋找效率太低了,而是優先在 Cache 中尋找。

Runtime 系統會把被調用的方法存到 Cache 中,如果一個方法被調用,那麼它有可能今後還會被調用,下次尋找的時候就會效率更高。就像電腦群組成原理中 CPU 繞過主存先訪問 Cache 一樣。 Property

typedef struct objc_property *Property;typedef struct objc_property *objc_property_t;//這個更常用

可以通過class_copyPropertyList 和 protocol_copyPropertyList 方法擷取類和協議中的屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

注意:
返回的是屬性列表,列表中每個元素都是一個 objc_property_t 指標

#import <Foundation/Foundation.h>@interface Person : NSObject/** 姓名 */@property (strong, nonatomic) NSString *name;/** age */@property (assign, nonatomic) int age;/** weight */@property (
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.