Runtime總結

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

這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼舶得。對于Objective-C來說梁肿。這個運行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運行,這個運行時系統(tǒng)即Objc RuntimeObjc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的,這個庫使得C語言有了面向對象的能力。

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

Class

Objective-C類是由Class類表示的,它實際是一個指向objc_class結構體的指針,它的定義如下:

typedef struct objc_class *Class;

objc/runtime.hobjc_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;

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

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是表示實例變量的類型,其實際是一個指向objc_ivar結構體的指針,其定義如下:

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
}

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

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

objc_object 與 id

objc_object是表示一個類的實例的結構體,它的定義如下(objc/objc.h):

struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};

typedef struct objc_object *id;

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

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

id,它實際上是一個objc_object結構類型的指針瞳腌。該類型的對象可以轉換為任何一種對象绞铃。
id類型是動態(tài)類型的,即運行時再決定對象的類型。id類型即通用的對象類,任何對象都可以被id指針所指,而在實際使用中,往往使用introspection來確定該對象的實際所屬類:

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ā)送消息(即調用類方法),如:

NSDictionary *dictionary = [NSDictionary dictionary];

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

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

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

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

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

消息處理

SEL

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

typedef struct objc_selector *SEL;

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

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

上面的輸出為:

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

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

- (void)dealTotalPriceWithProductCount:(int)productCount;

- (void)dealTotalPriceWithProductCount:(NSUInteger)productCount;

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

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

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

IMP

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

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

第一個參數(shù)是指向self的指針(如果是實例方法,則是類實例的內存地址;如果是類方法,則是指向元類的指針),第二個參數(shù)是方法選擇器(selector),接下來是方法的實際參數(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; // 方法實現(xiàn)
}

Method結構體包含一個SEL和 一個IMP,實際上相當 與在SELIMP之間作了一個映射。有了SEL,我們可以找到對應的IMP,從而調用方法的實現(xiàn)代碼沦童。

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

方法調用流程

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

objc_msgSend(receiver, selector)

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

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

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

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

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

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


消息傳遞

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

下面以實例對象調用方法[student speek]為例描述調用的流程:

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

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

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

Method Swizzing

Method swizzling 用于改變一個已經(jīng)存在的selector的實現(xiàn),這項技術使得在運行時候改變方法的調用成為可能聪黎。例如我們想要在一款iOS app中追蹤每一個界面呈現(xiàn)給力用戶多少次:可以通過在每個視圖控制器的viewDidAppear方法中添加追蹤代碼來實現(xiàn),但這樣會有大量重復的代碼,繼承也會有同樣的問題,利用method swizzling可以較完美實現(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ù)從當前對象中的method list獲取method結構體,
如果類方法那么就使用class_getClassMethod ( ) 函數(shù)獲取罕容。
*/
 Method originalMethod = class_getInstanceMethod(class, originalSelector); 
 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

/** 
* 我們在這里使用class_addMethod()函數(shù)對Method Swizzling做了一層驗證,如果self沒有實現(xiàn)被交換的方法稿饰,會導致失敗锦秒。
 * 而且self沒有交換的方法實現(xiàn),但是父類有這個方法喉镰,這樣就會調用父類的方法旅择,結果就不是我們想要的結果了。
 * 所以我們在這里通過class_addMethod()的驗證侣姆,如果self實現(xiàn)了這個方法生真,class_addMethod()函數(shù)將會返回NO沉噩,我們就可以對其進行交換了。
 */

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


 if (didAddMethod) { 

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

}

#pragma mark - Method Swizzling

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

@end

swizzling應該只在+load中完成柱蟀。在Object-C的運行時中,每個類都有兩個方法自動調用川蒙。
+load是在一個類被初始裝載時調用,+initialize是在應用應用第一次調用該類的類方法或實例
方法前調用。兩個方法都是可選的,并且只有在方法被實現(xiàn)的情況下才會被調用长已。

** swizzling 應該只在 dispatch_once 中完成**

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末恬汁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辜伟,更是在濱河造成了極大的恐慌蕊连,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件游昼,死亡現(xiàn)場離奇詭異甘苍,居然都是意外死亡,警方通過查閱死者的電腦和手機烘豌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門载庭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廊佩,你說我怎么就攤上這事囚聚。” “怎么了标锄?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵顽铸,是天一觀的道長。 經(jīng)常有香客問我料皇,道長谓松,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任践剂,我火速辦了婚禮鬼譬,結果婚禮上,老公的妹妹穿的比我還像新娘逊脯。我一直安慰自己优质,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巩螃,像睡著了一般演怎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上避乏,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天颤枪,我揣著相機與錄音,去河邊找鬼淑际。 笑死,一個胖子當著我的面吹牛扇住,可吹牛的內容都是我干的春缕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼艘蹋,長吁一口氣:“原來是場噩夢啊……” “哼锄贼!你這毒婦竟也來了?” 一聲冷哼從身側響起女阀,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宅荤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后浸策,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冯键,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年庸汗,在試婚紗的時候發(fā)現(xiàn)自己被綠了惫确。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚯舱,死狀恐怖改化,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情枉昏,我是刑警寧澤陈肛,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站兄裂,受9級特大地震影響句旱,放射性物質發(fā)生泄漏。R本人自食惡果不足惜晰奖,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一前翎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畅涂,春花似錦港华、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冒萄。三九已至,卻和暖如春橙数,著一層夾襖步出監(jiān)牢的瞬間尊流,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工灯帮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留崖技,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓钟哥,卻偏偏與公主長得像迎献,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腻贰,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容

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