Runtime 的理解

Runtime是深入學習OC的必經之路,所以花了一些時間研究了一下。

一、介紹RunTime(運行時):

OC 語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。

對于OC來說,這個運行時系統就像一個操作系統一樣:它讓所有的工作可以正常的運行。Runtime基本上是用C和匯編寫的,這個庫使得C語言有了面向對象的能力。

在Runtime中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝后,讓OC的面向對象編程變為可能。

找出方法的最終執行代碼:當程序執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。


二、類與對象的基礎數據結構

OC類是由Class類型來表示的,它實際上是一個指

向objc_class結構體的指針。

typedef struct object_class *Class

其定義如下:

查看objc/runtime.h中objc_class結構體的定義如下:

struct object_class{? ?

Class isa OBJC_ISA_AVAILABILITY;

#if!__OBJC2__

Class super_class? ? ? ? ? ? ? ? ? ? ? ?

OBJC2_UNAVAILABLE;// 父類constchar*name? ? ? ? ? ? ? ?

OBJC2_UNAVAILABLE;// 類名longversion

OBJC2_UNAVAILABLE;// 類的版本信息,默認為0longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 類信息,供運行期使用的一些位標識longinstance_size? ? ? ? ? ? ? ? ?? OBJC2_UNAVAILABLE;// 該類的實例變量大小structobjc_ivar_list *ivars? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類的成員變量鏈表structobjc_method_list *methodLists? ? OBJC2_UNAVAILABLE;// 方法定義的鏈表structobjc_cache *cache? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 方法緩存structobjc_protocol_list *protocols? ? OBJC2_UNAVAILABLE;// 協議鏈表#endif}OBJC2_UNAVAILABLE;


其執行過程:

NSArray *array = [[NSArray alloc] init];

objc_object

objc_object是表示一個類的實例的結構體

它的定義如下(objc/objc.h):

structobjc_object{? ? Class isa OBJC_ISA_AVAILABILITY;};typedefstructobjc_object*id;

可以看到,這個結構體只有一個字體,即指向其類的isa指針。這

樣,當我們向一個Objective-C對象發送消息時,運行時庫會根據

實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類

的方法列表及父類的方法列表中去尋找與消息對應的selector指向

的方法,找到后即運行這個方法。

元類(Meta Class)

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

在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發送消息(即調用類方法)。

既然是對象,那么它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那么,這個isa指針指向什么呢?

為了調用類方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念,meta-class中存儲著一個類的所有類方法。

所以,調用類方法的這個類對象的isa指針指向的就是meta-class

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

再深入一下,meta-class也是一個類,也可以向它發送一個消息,那么它的isa又是指向什么呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。

即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。

通過上面的描述,再加上對objc_class結構體中super_class指針的分析,我們就可以描繪出類及相應meta-class類的一個繼承體系了,如下代碼

Snip20160501_1.png

Category

Category是表示一個指向分類的結構體的指針,其定義如下:

typedefstructobjc_category*Categorystructobjc_category{char*category_name? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 分類名char*class_name? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 分類所屬的類名structobjc_method_list*instance_methods? OBJC2_UNAVAILABLE;// 實例方法列表structobjc_method_list*class_methods? ? ? OBJC2_UNAVAILABLE;// 類方法列表structobjc_protocol_list*protocols? ? ? ? OBJC2_UNAVAILABLE;// 分類所實現的協議列表}

這個結構體主要包含了分類定義的實例方法與類方法,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集。

可發現,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實例變量和屬性

structobjc_ivar_list*ivars? ? ? ? ? ? OBJC2_UNAVAILABLE;// 該類的成員變量鏈表

三、runtime關聯對象

我們先看看關聯API,只有這三個API,使用也是非常簡單的:

1.設置關聯值

參數說明:

object:與誰關聯,通常是傳self

key:唯一鍵,在獲取值時通過該鍵獲取,通常是使用static

const void *來聲明

value:關聯所設置的值

policy:內存管理策略,比如使用copy

voidobjc_setAssociatedObject(idobject,constvoid*key, idvalue, objc _AssociationPolicy policy)

2.獲取關聯值

參數說明:

object:與誰關聯,通常是傳self,在設置關聯時所指定的與哪個對象關聯的那個對象

key:唯一鍵,在設置關聯時所指定的鍵

idobjc_getAssociatedObject(idobject,constvoid*key)

3.取消關聯

voidobjc_removeAssociatedObjects(idobject)

關聯策略

使用場景:

可以在類別中添加屬性

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){OBJC_ASSOCIATION_ASSIGN =0,// 表示弱引用關聯,通常是基本數據類型OBJC_ASSOCIATION_RETAIN_NONATOMIC =1,// 表示強引用關聯對象,是線程安全的OBJC_ASSOCIATION_COPY_NONATOMIC =3,// 表示關聯對象copy,是線程安全的OBJC_ASSOCIATION_RETAIN =01401,// 表示強引用關聯對象,不是線程安全的OBJC_ASSOCIATION_COPY =01403// 表示關聯對象copy,不是線程安全的};

四、方法與消息

1、SEL

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

typedefstructobjc_selector *SEL;

方法的selector用于表示運行時方法的名字。Objective-C在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL。

兩個類之間,只要方法名相同,那么方法的SEL就是一樣的,每一個方法都對應著一個SEL。所以在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,即使參數類型不同也不行

如在某一個類中定義以下兩個方法: 錯誤

- (void)setWidth:(int)width;- (void)setWidth:(double)width;

當然,不同的類可以擁有相同的selector,這個沒有問題。不同類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。

工程中的所有的SEL組成一個Set集合,如果我們想到這個方法集合中查找某個方法時,只需要去找到這個方法對應的SEL就行了,SEL實際上就是根據方法名hash化了的一個字符串,而對于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度上無語倫比!

本質上,SEL只是一個指向方法的指針(準確的說,只是一個根據方法名hash化了的KEY值,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度。

通過下面三種方法可以獲取SEL:

a、sel_registerName函數

b、Objective-C編譯器提供的@selector()

c、NSSelectorFromString()方法

2、IMP

IMP實際上是一個函數指針,指向方法實現的地址。

其定義如下:

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

第一個參數:是指向self的指針(如果是實例方法,則是類實例的內存地址;如果是類方法,則是指向元類的指針)

第二個參數:是方法選擇器(selector)

接下來的參數:方法的參數列表。

前面介紹過的SEL就是為了查找方法的最終實現IMP的。由于每個方法對應唯一的SEL,因此我們可以通過SEL方便快速準確地獲得它所對應的IMP,查找過程將在下面討論。取得IMP后,我們就獲得了執行這個方法代碼的入口點,此時,我們就可以像調用普通的C語言函數一樣來使用這個函數指針了。

3、Method

Method用于表示類定義中的方法,則定義如下:

typedef struct objc_method *Methodstructobjc_method{

SEL method_name? ? ? OBJC2_UNAVAILABLE; // 方法名

char *method_types? OBJC2_UNAVAILABLE;

IMP method_imp? ? ? OBJC2_UNAVAILABLE; // 方法實現

}

我們可以看到該結構體中包含一個SEL和IMP,實際上相當于在SEL和IMP之間作了一個映射。有了SEL,我們便可以找到對應的IMP,從而調用方法的實現代碼。

4、方法調用流程

Snip20160501_2.png

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

objc_msgSend(receiver, selector)

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

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

這個函數完成了動態綁定的所有事情:

a、首先它找到selector對應的方法實現。因為同一個方法可

能在不同的類中有不同的實現,所以我們需要依賴于接收者的類

來找到的確切的實現。

b、調用方法實現,并將接收者對象及方法的所有參數傳給它。

c、最后,它將實現返回的值作為它自己的返回值。

消息的關鍵在于我們前面章節討論過的結構體objc_class,這個結構體有兩個字段是我們在分發消息的關注的:

-> 指向父類的指針

-> 個類的方法分發表,即methodLists。

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

下圖演示了這樣一個消息的基本框架:

當消息發送給一個對象時首先從運行時系統緩存使用過的方法中尋找。

如果找到,執行該方法,如未找到繼續執行下面的步驟

objc_msgSend通過對象的isa指針獲取到類的結構體,然后在方法分發表里面查找方法的selector。

如果沒有找到selector,objc_msgSend結構體中的指向父類的指針找到其父類,并在父類的分發表里面查找方法的selector。

依此,會一直沿著類的繼承體系到達NSObject類。一旦定位到selector,函數會就獲取到了實現的入口點,并傳入相應的參數來執行方法的具體實現,并將該方法添加進入緩存中如果最后沒有定位到selector,則會走消息轉發流程,這個我們在后面討論。

5、消息轉發

當一個對象能接收一個消息時,就會走正常的方法調用流程。但如果一個對象無法接收指定消息時,又會發生什么事呢?默認情況下,如果是以[object message]的方式調用方法,如果object無法響應message消息時,編譯器會報錯。但如果是以perform…的形式來調用,則需要等到運行時才能確定object是否能接收message消息。如果不能,則程序崩潰。

Snip20160501_3.png

通常,當我們不能確定一個對象是否能接收某個消息時,會先調用respondsToSelector:來判斷一下。如下代碼所示:

if([selfrespondsToSelector:@selector(method)]){[self performSelector:@selector(method)];}

不過,我們這邊想討論下不使用respondsToSelector:判斷的情況。這才是我們這一節的重點。

當一個對象無法接收某一消息時,就會啟動所謂“消息轉發(message forwarding)”機制,通過這一機制,我們可以告訴對象如何處理未知的消息。默認情況下,對象接收到未知的消息,會導致程序崩潰,通過控制臺,我們可以看到以下異常信息:

這段異常信息實際上是由NSObject的“doesNotRecognizeSelector”方法拋出的。不過,我們可以采取一些措施,讓我們的程序執行特定的邏輯,而避免程序的崩潰。

消息轉發機制基本上分為三個步驟:

1>、動態方法解析

2>、備用接收者

3>、完整轉發

消息的轉發流程圖:

Snip20160501_5.png

動態方法解析

對象在接收到未知的消息時,首先會調用所屬類的類方法

+resolveInstanceMethod:(實例方法)或者

+resolveClassMethod:(類方法)。

在這個方法中,我們有機會為該未知消息新增一個“處理方法”,通過運行時class_addMethod函數動態添加到類里面就可以了。

這種方案更多的是為了實現@dynamic屬性。

備用接收者

-(id)forwardingTargetForSelector:(SEL)aSelector

如果在上一步無法處理消息,則Runtime會繼續調以下方法:

如果一個對象實現了這個方法,并返回一個非nil的結果,則這個對象會作為消息的新接收者,且消息會被分發到這個對象。當然這個對象不能是self自身,否則就是出現無限循環。當然,如果我們沒有指定相應的對象來處理aSelector,則應該調用父類的實現來返回結果。

這一步合適于我們只想將消息轉發到另一個能處理該消息的對象上。但這一步無法對消息進行處理,如操作消息的參數和返回值。

完整消息轉發

如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉發機制了。

我們首先要通過,指定方法簽名,若返回nil,則表示不處理。

如下代碼:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{if([NSStringFromSelector(aSelector)isEqualToString:@"testInstanceMethod"]){return[NSMethodSignaturesignatureWithObjcTypes:"v@:"];? }return[supermethodSignatureForSelector:aSelector];}

若返回方法簽名,則會進入下一步調用以下方法,對象會創建一個表示消息的NSInvocation對象,把與尚未處理的消息有關的全部細節都封裝在anInvocation中,包括selector,目標(target)和參數。

我們可以在forwardInvocation方法中選擇將消息轉發給其它對象。我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等.

如下所示:

- (void)forwardInvovation:(NSInvocation)anInvocation{? ? [anInvocationinvokeWithTarget:_helper];? ? [anInvocationsetSelector:@selector(run)];? ? [anInvocationinvokeWithTarget:self];}

五、Method Swizzling

1.Swizzling原理

在Objective-C中調用一個方法,其實是向一個對象發送消息,而查找消息的唯一依據是selector的名字。所以,我們可以利用Objective-C的runtime機制,實現在運行時交換selector對應的方法實現以達到我們的目的。

每個類都有一個方法列表,存放著selector的名字和方法實現的映射關系。IMP有點類似函數指針,指向具體的Method實現

我們先看看SEL與IMP之間的關系圖:

Snip20160501_6.png

從上圖可以看出來,每一個SEL與一個IMP一一對應,正常情況下通過SEL可以查找到對應消息的IMP實現。

但是,現在我們要做的就是把鏈接線解開,然后連到我們自定義的函數的IMP上。當然,交換了兩個SEL的IMP,還是可以再次交換回來了。交換后變成這樣的,如下圖

Snip20160501_7.png

從圖中可以看出,我們通過swizzling特性,將selectorC的方法實現IMPc與selectorN的方法實現IMPn交換了,當我們調用selectorC,也就是給對象發送selectorC消息時,所查找到的對應的方法實現就是IMPn而不是IMPc了。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,227評論 0 7
  • 轉載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 756評論 0 2
  • 本文轉載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 772評論 0 1
  • 前言 runtime其實在我們日常開發過程中很少使用到,尤其是像我現在比較初級的程序猿就更用不到了。但是去面試很多...
    WolfTin閱讀 649評論 0 2