iOS筆記-Runtime運行時

創(chuàng)建時間: 2016-12-12
狀態(tài):發(fā)布

簡介

Runtime又叫運行時,是一套底層C語言的API,是iOS核心,平時編寫的OC代碼,底層都是基于它實現(xiàn)的。

[receiver message];
// 底層會被編譯器轉(zhuǎn)換為這樣再運行
objc_mesgSend(receiver, selector)
// 如果帶有參數(shù)
[receiver message:(id)arg...];
// 底層會被編譯器轉(zhuǎn)換為這樣再運行
objc_mesgSend(receiver, selector, arg1, arg2,...)

可能光看代碼看不出來他的價值,但是需Objective-C是一門動態(tài)語言,會將一些工作放在代碼運行時才處理,而不是在編譯的時候。即:很多類的成員變量在編譯的時候系統(tǒng)是不知道的,只有在運行時,加入內(nèi)存中才能知道。在運行時,我們所編寫的代碼會轉(zhuǎn)換成完整的能夠執(zhí)行的底層代碼再運行
因此,單單擁有編譯器是遠遠不夠的,還需要一個運行時系統(tǒng)Runtime system來處理編譯后的代碼。
Runtime基本是用C和匯編寫的,蘋果和GNU各自維護一個開源的Runtime版本,這兩個版本也在努力保持一致。

Runtime的作用

Objc在三種層面上與Runtime系統(tǒng)進行交互:

  1. 通過Objective-C源碼
  2. 通過Foundation框架的NSObject類定義的方法
  3. 通過對Runtime庫函數(shù)的直接調(diào)用

Objective-C源代碼

多數(shù)情況下,Runtime系統(tǒng)會自動在幕后幫助我們做完從OC語言到C執(zhí)行語言的轉(zhuǎn)換工作,在運行時確定數(shù)據(jù)結(jié)構(gòu)和函數(shù)

通過Foundation框架的NSObject類定義的方法和Runtime交互

Cocoa程序中大部分的類都是NSObject類的子類,繼承了它的行為。(NSProxy類例外,它是個抽象超類)
一些情況下,NSObject類僅僅定義了完成某件事情的模板,并沒有提供所需要的代碼。如- description方法,該方法返回類內(nèi)容的字符串標識,一般在模型中重寫這個方法,輸出模型內(nèi)容并調(diào)試程序。NSObject類并不知道子類的內(nèi)容,如模型類,所以它只是返回類的名字和對象的地址,NSObject的子類可以重新實現(xiàn)。
還有一些NSObject的方法可以從Runtime系統(tǒng)中獲取信息,運行對象進行自我檢查
如:

  • - class返回對象的類
  • - isKindOfClass:- isMemberOfClass:方法檢查對象是否存在于指定的類的繼承體系中(子類或父類 / 當前類的成員變量)
  • - respondsToSelector:檢查對象能否響應指定的消息(一般是在代理模式中判斷代理是否實現(xiàn)類代理方法)
  • - conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法
  • - methodForSelector:返回指定方法實現(xiàn)的地址

通過對Runtime庫函數(shù)直接調(diào)用

Runtime系統(tǒng)是具有公用接口的動態(tài)共享庫。頭文件存放在/usr/include/objc目錄下,這以為這我們使用時只需要引入objc/Runtime.h頭文件即可
許多函數(shù)可以讓你使用純C代碼來實現(xiàn)Objc中同樣的功能。除非是寫一些Objc與其他語言的橋接或是底層的debug工作,在寫Objc代碼時一般不會用到這些C語言函數(shù)。

一些Runtime術(shù)語的數(shù)據(jù)結(jié)構(gòu)

常見的Runtime術(shù)語

  • SEL
  • id
  • Class
  • Method
  • Ivar
  • IMP
  • Cache
  • Property

SEL

selector在Objec中表示(Swift中是Selector類)selector是方法的選擇器,作用和名字一樣,日常生活中,我們通過人名來辨別不同的人,注意Objc在相同的類中不會有兩個命名相同的方法,也就是說,會通過方法名來辨別不同的方法。selector對方法名進行包裝,以便找到對應的方法實現(xiàn)。其結(jié)構(gòu)是:

typedef struct objc_selector *SEL; // 結(jié)構(gòu)體

可以看出他是個映射到方法的C字符串,可以通過Objc編譯器命令@selector()或Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個SEL類型的方法選擇器

注意
不同的類中相同名字的方法所對應的selector是相同的,由于變量的類型不同,所以不會導致他們調(diào)用方法實現(xiàn)混亂

id

id 是一個參數(shù)類型,他是指向某個類的示例的指針,定義如下

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

以上定義,看到objc_object結(jié)構(gòu)體包含一個isa指針,根據(jù)isa指針就可以找到對象所屬的類

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

PS: KVO的時間機制就是將被觀察對象的isa指針指向一個中間類而不是真實類型:KVO

Class

typedef struct objc_class *Class;

Class其實是指向objc_class結(jié)構(gòu)體的指針,objc_class的數(shù)據(jù)結(jié)構(gòu)如下

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可以看到,一個運行時類中關聯(lián)了它的父類指針、類名、成員變量、方法、緩存及附屬的協(xié)議
其中objc_ivar_listobjc_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;
}

由此可見,我們可以動態(tài)修改*methodList的值來添加成員方法,這也是Category實現(xiàn)的原理,同樣解釋了Category不能添加屬性的原因。美團技術(shù)團隊的文章:深入理解Objective-C
objc_ivar_list 結(jié)構(gòu)體用來存儲成員變量的列表,而 objc_ivar 則是存儲了單個成員變量的信息;同理,objc_method_list 結(jié)構(gòu)體存儲著方法數(shù)組的列表,而單個方法的信息則由 objc_method 結(jié)構(gòu)體存儲。
值得注意的時,objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。為了處理類和對象的關系,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的元數(shù)據(jù)。
我們所熟悉的類方法,就源自于 Meta Class。我們可以理解為類方法就是類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。
當你發(fā)出一個類似 NSObject alloc 的消息時,實際上,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類(Root Meta Class)的實例。所有元類的 isa 指針最終都指向根元類。
所以當 [NSObject alloc] 這條消息發(fā)送給類對象的時候,運行時代碼 objc_msgSend() 會去它元類中查找能夠響應消息的方法實現(xiàn),如果找到了,就會對這個類對象執(zhí)行方法調(diào)用。

alloc

上圖實現(xiàn)是 super_class 指針,虛線時 isa 指針。而根元類的父類是 NSObject,isa指向了自己。而 NSObject 沒有父類。
最后 objc_class 中還有一個 objc_cache ,緩存,它的作用很重要

Category擴展

Objective-C的這個語言特性對于純動態(tài)語言來說可能不算什么,比如javascript,你可以隨時為一個“類”或者對象添加任意方法和實例變量。但是對于不是那么“動態(tài)”的語言而言,這確實是一個了不起的特性。
objc_ivar_list 結(jié)構(gòu)體用來存儲成員變量的列表,而 objc_ivar 則是存儲了單個成員變量的信息;同理,objc_method_list 結(jié)構(gòu)體存儲著方法數(shù)組的列表,而單個方法的信息則由 objc_method 結(jié)構(gòu)體存儲。
值得注意的時,objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。為了處理類和對象的關系,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的元數(shù)據(jù)。
我們所熟悉的類方法,就源自于 Meta Class。我們可以理解為類方法就是類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。
當你發(fā)出一個類似 NSObject alloc 的消息時,實際上,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類(Root Meta Class)的實例。所有元類的 isa 指針最終都指向根元類。
所以當 [NSObject alloc] 這條消息發(fā)送給類對象的時候,運行時代碼 objc_msgSend() 會去它元類中查找能夠響應消息的方法實現(xiàn),如果找到了,就會對這個類對象執(zhí)行方法調(diào)用。

Category和Extension

extension看起來很像一個匿名的category,但是extension和有名字的category幾乎完全是兩個東西。 extension在編譯期決議,它就是類的一部分,在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension。
但是category則完全不一樣,它是在運行期決議的。
就category和extension的區(qū)別來看,我們可以推導出一個明顯的事實,extension可以添加實例變量,而category是無法添加實例變量的(因為在運行期,對象的內(nèi)存布局已經(jīng)確定,如果添加實例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災難性的)。


參考:

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

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

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