Runtime總結(jié)

Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放在運(yùn)行時來處理,這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時更具有靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象或隨意交換一個方法的實(shí)現(xiàn)。

這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運(yùn)行時系統(tǒng)來執(zhí)行編譯的代碼。對于Objective-C來說。這個運(yùn)行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行,這個運(yùn)行時系統(tǒng)即Objc Runtime。Objc Runtime其實(shí)是一個Runtime庫,它基本上是用C和匯編寫的,這個庫使得C語言有了面向?qū)ο蟮哪芰Α?/p>

Runtime庫主要做下面幾件事:
1.封裝:在這個庫中,對象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實(shí)現(xiàn),另外加一些額外的特性。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們可以在程序運(yùn)行時創(chuàng)建,檢查,修改類對象和它們的方法了。
2.找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object dosomething]時,會向消息接受者(object)發(fā)送一條信息(doSomething),RunTime會根據(jù)消息接受者是否能響應(yīng)消息做出不同的反應(yīng)。

Class

Objective-C類是由Class類表示的,它實(shí)際是一個指向objc_class結(jié)構(gòu)體的指針,它的定義如下:

typedef struct objc_class *Class;

objc/runtime.hobjc_class結(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;

1.isa:在Object-C中,所有的類的自身也是一個對象,類和類的實(shí)例沒有任何本質(zhì)上的區(qū)別,任何對象都有isa的指針。isa是一個Class類型的指針,每個實(shí)例對象都有一個isa的指針,他指向?qū)ο蟮念?而類(Class)里也有個isa的指針,指向meteClass(元類)。
2.super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject)則super_class為NULL。
3.char *name: 類名。
4.version:我們可以使用這個字段來提供類的版本信息。
5.info運(yùn)行期使用的一些位標(biāo)識。
6.instance_size: 該類的實(shí)例變量大小。
7.ivars:objc_ivar_list結(jié)構(gòu)體存儲著objc_ivar成員變量數(shù)組列表,而'obj_ivar'結(jié)構(gòu)體存儲了類的單個成員變量的信息。

objec_class中,所有得到成員變量,屬性是放在鏈表ivars中的。ivars是一個數(shù)組,數(shù)組中每個元素都指向Ivar(變量信息)的指針。

objc_ivar_list *ivars

struct objc_ivar_list {

int ivar_count OBJC2_UNAVAILABLE;

ifdef LP64int space OBJC2_UNAVAILABLE;

endif/* variable length structure */

struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;

} OBJC2_UNAVAILABLE;

Ivar是表示實(shí)例變量的類型,其實(shí)際是一個指向objc_ivar結(jié)構(gòu)體的指針,其定義如下:

typedef struct objc_ivar *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
}

從上面可以看出類的實(shí)例變量和屬性經(jīng)過runtime經(jīng)過struct的儲存形式存在,并且單個實(shí)例變量保存其名字、類型、偏移量和儲存空間。類中所有實(shí)例變量是以list類型進(jìn)行儲存。

8.objc_method_listObj_method方法列表。
9.cache:用于緩存最近使用的方法,一個接收者對象收到一個消息時,會根據(jù)isa指針去查找能夠響應(yīng)這個消息的對象,但是在實(shí)際使用中,這個對象只有一部分方法是常用的,很多方法很少或者根本用不上,這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差,這時cache就有用了,在我們每次調(diào)用 一個方法后,這個方法就會被緩存到cache列表中,下次調(diào)用的時候 runtime就會優(yōu)先去cache中找,如果cache沒有,才會去methodLists中查找方法。

objc_object 與 id

objc_object是表示一個類的實(shí)例的結(jié)構(gòu)體,它的定義如下(objc/objc.h):

struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};

typedef struct objc_object *id;

可以看到,objc_object這個結(jié)構(gòu)體只有一個字體Class isa OBJC_ISA_AVAILABILITY是指向其類的isa指針,這樣當(dāng)我們向一個Objective-C對象發(fā)送消息時,Runtime庫會根據(jù)實(shí)例對象的isa指針找到這個實(shí)例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應(yīng)的selector指向的方法。找到后運(yùn)行這個方法。

當(dāng)創(chuàng)建一個特定類的實(shí)例 對象時,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實(shí)例變量的數(shù)據(jù),NSObject類的allocallocWithZone方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。

id,它實(shí)際上是一個objc_object結(jié)構(gòu)類型的指針。該類型的對象可以轉(zhuǎn)換為任何一種對象。
id類型是動態(tài)類型的,即運(yùn)行時再決定對象的類型。id類型即通用的對象類,任何對象都可以被id指針?biāo)?而在實(shí)際使用中,往往使用introspection來確定該對象的實(shí)際所屬類:

id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
    someClass *classSpecifiedInstance = (someClass *)obj;
    // Do Something to classSpecifiedInstance which now is an instance of someClass
    //...
}

元類(Meal Class)

所有的類的自身也是一個對象,我們可以向這個對象發(fā)送消息(即調(diào)用類方法),如:

NSDictionary *dictionary = [NSDictionary dictionary];

+dictionary消息發(fā)送給了NSDictionary類,而這個NSDictionary也是一個
對象,既然是對象,那么它也是一個objc_object指針,它包含一個指向其類的一個isa指針。為了調(diào)用+dictionary方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結(jié)構(gòu)體。這就引出了meta-class的概念

met-class是一個類對象的類。

當(dāng)我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而像一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找。

meta-class它存儲著一個類的所有類方法。每個類都會有一個單獨(dú)的meta-class,每個類的類方法基本不可能完全相同。

meta-class也是一個,也可以向它發(fā)送一個消息,那么它的isa又是指向哪里,為了不讓這種結(jié)構(gòu)無限延伸下去,Object-C的設(shè)計者讓所有的meta-classisa指向基類的meta-class,以此作為它們的所屬類。即任何NSObject繼承體系下的meta-class都會使用NSObject的meta-class作為自己所屬類,而基類的isa指針是指向它自己。這樣就形成了一個完美的閉環(huán)

消息處理

SEL

SEL又叫選擇器,是表示一個方法的selector的指針,其定義如下:

typedef struct objc_selector *SEL;

selector用于表示運(yùn)行時方法的名字,Objective-C編譯時,會根據(jù)每一個方法的名字、參數(shù)序列,生成一個唯一整型標(biāo)識(Int類型的地址),這個標(biāo)識就是SEL,如下代碼所示:

SEL sel = @selector(method);
NSLog(@"sel : %p", sel);

上面的輸出為:

2016-11-28 14:04:41.151 RuntimeDemo[89015:1134944] sel : 0x103076a22**

兩個類之間,不管它們是父類與子類的關(guān)系,還是之間沒有這種關(guān)系,只要方法名相同,那么SEL就是一樣的,每一個方法都對應(yīng)著一個SEL。所以在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,即使參數(shù)類型不同也不行。相同的方法只能對應(yīng)一個SEL。這就導(dǎo)致Objecttive-C在處理相同方法名字且參數(shù)個數(shù)相同但是參數(shù)類型不同的方法方面的能力很差。如:

- (void)dealTotalPriceWithProductCount:(int)productCount;

- (void)dealTotalPriceWithProductCount:(NSUInteger)productCount;

這種定義會被編譯器認(rèn)為是一種編譯錯誤。不同類的實(shí)例對象執(zhí)行相同的selector時,會在各自的方法列表中去根據(jù)selector去尋找自己對應(yīng)的IMP。

在一個工程中所有的SEL組成一個Set集合,Set 的特點(diǎn)是唯一,因此SEL是唯一的。因此,如果我們想到這個方法集合 中查找某個方法時,只要去找到這個方法對應(yīng)的SEL就行了,SEL實(shí)際上就是根據(jù)方法名hash化了一個字符串,而對字符串的比較僅僅需要比較它們的地址就可以了,速度上是非??斓?。
本質(zhì)上,SEL只是一個指向方法的指針(準(zhǔn)確說,只是一個方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度。

我們可以在運(yùn)行時添加新的selector,也可以在運(yùn)行時獲取已存在的selector,有三種方法來獲取SEL:
1.sel_registerName函數(shù)
2.Objective-C編譯器提供的@selector()
3.nSSelectorFromString()方法

IMP

IMP實(shí)際上是一個函數(shù)指針,指向方法實(shí)現(xiàn)的首地址。它是一個函數(shù)指針,由編譯器生成,SEL就是為了查找方法的最終實(shí)現(xiàn)IMP。每個方法對應(yīng)唯一的SEL,因此我們可以快速準(zhǔn)確地獲得它對應(yīng)的IMP,取得IMP后,我們就獲得了執(zhí)行這個方法代碼的入口點(diǎn),通過取得IMP,我們可以跳過Runtime的消息傳遞機(jī)制,直接執(zhí)行IMP指向的函數(shù)實(shí)現(xiàn),這樣就省去了Runtime消息傳遞過程中的一系列查找操作,會比直接向?qū)ο蟀l(fā)送消息高效一些。

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

第一個參數(shù)是指向self的指針(如果是實(shí)例方法,則是類實(shí)例的內(nèi)存地址;如果是類方法,則是指向元類的指針),第二個參數(shù)是方法選擇器(selector),接下來是方法的實(shí)際參數(shù)列表。

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; // 方法實(shí)現(xiàn)
}

Method結(jié)構(gòu)體包含一個SEL和 一個IMP,實(shí)際上相當(dāng) 與在SELIMP之間作了一個映射。有了SEL,我們可以找到對應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼。

SEL:代表方法名類型,在不同的類中定義,它們的方法選擇器也不一樣。
method_types : method_types方法類型是一個char指針,存儲著方法的參數(shù)類型和返回類型。
method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個函數(shù)指針。

方法調(diào)用流程

在Objective-C,消息直到運(yùn)行時才綁定到方法實(shí)現(xiàn)上,編譯器會將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù),如下:

objc_msgSend(receiver, selector)

如果消息中還有其它參數(shù),則該方法的形式如下所示:

objc_msgSend(receiver, selector, arg1, arg2, ...)

這個函數(shù)完成了動態(tài)綁定的所有事情:
1..首先找到selector`對應(yīng)的方法實(shí)現(xiàn)。因為同一個方法可能在不同的類中有不同的實(shí)現(xiàn),所以我們需要依賴接受者的類來找到確切的實(shí)現(xiàn)。
2.它調(diào)用方法實(shí)現(xiàn),并將接受者對象及方法的所有參數(shù)傳給它。
3.最后,它將實(shí)現(xiàn)返回的值作為自己的返回值。

消息的關(guān)鍵在于前面有解釋的結(jié)構(gòu)體objc_class,這個結(jié)構(gòu)體有連個字段是我們在分發(fā)消息的時候需要關(guān)注的:

1.指向父類的指針
2.一個類的方法分發(fā)表,即methodList。

當(dāng)我們創(chuàng)建一個新對象的時候,先為其分配內(nèi)存,并初始化其成員變量。其中isa指針也會被初始化,讓對象可以訪問類及類得得繼承體系。


消息傳遞

當(dāng)消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結(jié)構(gòu)體,然后在方法分發(fā)表里查找方法的selector。如果沒有找到selector,則通過objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類,并在父類的分發(fā)表里面查找方法的selector。依次,會一直沿著類的繼承體系到達(dá)NSObject類。一旦定位到selector,函數(shù)就獲取到了實(shí)現(xiàn)得得入口點(diǎn),并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)。如果沒有定位到selector,為了加速消息的處理,運(yùn)行時系統(tǒng)緩存使用過的'selector'級對應(yīng)的方法的地址。

下面以實(shí)例對象調(diào)用方法[student speek]為例描述調(diào)用的流程:

1.編譯器會把`[student speak]`轉(zhuǎn)化為`objc_msgSend(student, SEL)`,SEL為@selector(speek)。
2.runtime會在student對象對應(yīng)的Student類的方法緩存列表里查找方法的SEL。
3.如果沒有找到,則在Student類的方法分發(fā)表查找SEL,類對象由對象isa指針指向,分發(fā)列表即methodList。
4.如果沒有找到,則在父類(設(shè)Student的父類是Person類)的方法分發(fā)表里查找方法的SEL(父類由類的superClass指向)。
5.如果沒有找到,則沿著繼承體系繼續(xù)找下去,最終到達(dá)NSObject類停止。
6.如果在2 、3 、4的其中一步找到,會通過SEL找打?qū)?yīng)的IMP,即定位到了方法實(shí)現(xiàn)的入口,執(zhí)行具體實(shí)現(xiàn)。
7.如果最后還是沒有找到,則會進(jìn)行消息轉(zhuǎn)發(fā)。

獲取方法地址
Runtime中方法的動態(tài)綁讓我們寫代碼的時候更具有靈活性,如我們可以消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個方法的實(shí)現(xiàn)等動態(tài)綁定不過靈活性的提升也帶來了性能上的一些損耗。畢竟我們需要去查找方法的實(shí)現(xiàn)。而不像函數(shù)調(diào)用得那么直接。當(dāng)然方法得當(dāng)緩存一定程度上解決了這一問題。

如果想要避開這種動態(tài)綁定方式,我們可以獲取方法實(shí)

Method Swizzing

Method swizzling 用于改變一個已經(jīng)存在的selector的實(shí)現(xiàn),這項技術(shù)使得在運(yùn)行時候改變方法的調(diào)用成為可能。例如我們想要在一款iOS app中追蹤每一個界面呈現(xiàn)給力用戶多少次:可以通過在每個視圖控制器的viewDidAppear方法中添加追蹤代碼來實(shí)現(xiàn),但這樣會有大量重復(fù)的代碼,繼承也會有同樣的問題,利用method swizzling可以較完美實(shí)現(xiàn):

#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load { 

 static dispatch_once_t onceToken; 
 dispatch_once(&onceToken, ^{ 
 Class class = [self class]; 

//源方法的SEL
 SEL originalSelector = @selector(viewWillAppear:); 
//交換方法的SEL
 SEL swizzledSelector = @selector(prefix_viewWillAppear:);

/*
通過class_getInstanceMethod( ) 函數(shù)從當(dāng)前對象中的method list獲取method結(jié)構(gòu)體,
如果類方法那么就使用class_getClassMethod ( ) 函數(shù)獲取。
*/
 Method originalMethod = class_getInstanceMethod(class, originalSelector); 
 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

/** 
* 我們在這里使用class_addMethod()函數(shù)對Method Swizzling做了一層驗證,如果self沒有實(shí)現(xiàn)被交換的方法,會導(dǎo)致失敗。
 * 而且self沒有交換的方法實(shí)現(xiàn),但是父類有這個方法,這樣就會調(diào)用父類的方法,結(jié)果就不是我們想要的結(jié)果了。
 * 所以我們在這里通過class_addMethod()的驗證,如果self實(shí)現(xiàn)了這個方法,class_addMethod()函數(shù)將會返回NO,我們就可以對其進(jìn)行交換了。
 */

BOOL didAddMethod = class_addMethod(class, 
                                    originalSelector, 
                                    method_getImplementation(swizzledMethod),  
                                    method_getTypeEncoding(swizzledMethod)
                                    );


 if (didAddMethod) { 

//添加成功:將源方法的實(shí)現(xiàn)替換到交換方法的實(shí)現(xiàn)
  class_replaceMethod(class,
                      swizzledSelector, 
                      method_getImplementation(originalMethod), 
                      method_getTypeEncoding(originalMethod)
                      ); 
   } else {
//添加失敗:  說明源方法已經(jīng)有實(shí)現(xiàn), 直接將兩個方法的實(shí)現(xiàn)交換即可
     method_exchangeImplementations(originalMethod, swizzledMethod); 
} 
});

}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
 [self xxx_viewWillAppear:animated];
 NSLog(@"viewWillAppear: %@", self);
}

@end

swizzling應(yīng)該只在+load中完成。在Object-C的運(yùn)行時中,每個類都有兩個方法自動調(diào)用。
+load是在一個類被初始裝載時調(diào)用,+initialize是在應(yīng)用應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗?br> 方法前調(diào)用。兩個方法都是可選的,并且只有在方法被實(shí)現(xiàn)的情況下才會被調(diào)用。

** swizzling 應(yīng)該只在 dispatch_once 中完成**

由于 swizzling 改變了全局的狀態(tài),所以我們需要確保每個預(yù)防措施在運(yùn)行時都是可用的。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預(yù)防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once 滿足了所需要的需求,并且應(yīng)該被當(dāng)做使用 swizzling 的初始化單例方法的標(biāo)準(zhǔn)。

最后編輯于
?著作權(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)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,051評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,887評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,334評論 0 7
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,249評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 832評論 0 2

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