runtime原理分析

runtime是oc中一個比較底層的純c語言庫,包含了很多底層c語言的api

runtime其實和我們編程密切相關,平時編寫的oc代碼中,在程序運行時,其實最終都是轉成runtime的c語言代碼


可以這么理解,oc的底層實際上是在調用一個個c函數,如何找到這些c函數的工作,就是由runtime去負責了


要理解runtime,首先要理解類的結構


類的本質是什么? 實際上就是一個類似結構體的玩意,那么類里面有什么內容呢,以下一張圖就能夠很清晰的理解


一個Person類的內部構造

isa就是“is a”,對于所有繼承了NSObject的類其對象也都有一個isa指針。這個isa指針指向關于這個對象所屬的類的定義。?

任何直接或間接繼承了NSObject的類,它的實例對象(instacne objec)中都有一個isa指針,指向它的類對象(class object)。這個類對象(class object)中存儲了關于這個實例對象(instace object)所屬的類的定義的一切:包括變量,方法,遵守的協議等等

以下這圖可以很清楚的表述上面的關系

Class isa and superclass relationship from Google

簡單來說,一個類的對象的isa指針會指向這個類,這個類也有一個isa指針會指向這個類的元祖類(meta class)。

一個類以及其元祖類(meta class) 都會有一個super class 的指針指向其父類,一直到根類(NSObject),NSObject的元祖類的super class 會指回到NSObject類,至于NSObject是沒有super class 的,其super class 指針會指向nil

至于類和元祖類是什么關系呢? 首先,他們都是對象,根本上都是objc_class對象,因此也有類對象和元祖類對象的一說。區別在于,類對象中包含了這個類的實例變量,實例方法的定義,而元祖類對象中包含類的方法的定義。


搞清楚對象和類的這層關系之后,要理解起來就輕松多了,你可以理解成isa 和 super class 就是一個門牌號或者路標,runtime就是根據這些路標去找到相應的靜態方法和變量的。

-------------------------------------- 華麗的分割線 ? ----------------------------------

搞清楚指針之后,之后就看重點了

ivars(成員變量列表)

? ? ? ? 這個屬性 objc_ivar_list 結構體類型,其實就是一個鏈表,存儲多個objc_ivar,而objc_ivar又是一個結構體,用來存儲類的單個成員變量的信息

? ? ? ? ?簡單粗暴一點理解,ivar 就是成員變量

objc_ivar里面又有什么內容呢?見下圖


objc_ivar結構


methodlists(方法列表)

? ? ? 同ivars一樣理解,實際上這兩個結構體幾乎是一樣的,也是一個objc_method_list 的結構體, 也是一個鏈表,存儲多個objc_method, ?而objc_method 又是一個結構體,用來存儲類的某個方法信息

我們也可以來一張圖來展示objc_method的風采


objc_method

看到SEL估計都不陌生了,一個方法的SEL 其實是一個C的字符串,并且會在OC的runtime中注冊這個selector,這個操作一般是在加載到內存的時候就完成了這一步注冊操作,即在+ load 的方法中

實際上我們平時調用方法,都不是一步到位,實際上是通過selector找到該方法,然后再找到這個方法的實現(IMP),IMP本質上就是一個函數指針,指向c函數

舉個栗子方便理解:

[ a ?sayHello] ?---> 在a對象中通過其isa指針找到其類對象--->找到methodList ---> 根據"sayHello"這個selector找到對應的方法 ---> 找到其對應的IMP指針 ?---> ?c函數

以上僅僅是方便理解而已,因為實際上,上面調用方法的環節還要加入緩存池的操作(cache)


Cache(方法緩存池)

? ? ? ? ? 二話不說,先上圖


objc_cache

? ? ? ? ? ?Cache其實就是一個存儲Method的鏈表,主要是為了優化方法調用的性能。當調用一個方法的時候,會先去緩存池找,如果沒找到,再去methodLists查找


------------------------------------------又是華麗的分割線-----------------------------


消息發送

?當調用一個方法時[a sayHello],其實是被編譯器轉化為

objc_msgSend ( id self, SEL op, ... )

1.根據a實例對象的isa找到其對應的類對象

2.優先在類對象中的cache查找sayHello方法,如果找不到,再到methodLists查找

3.如果找不到,通過super_class 指針向父類查找,如果找不到,找父類的父類,一直到NSObject

4.一旦找到了,則執行IMP的實現


容錯機制

要是找不到怎么辦?一開始我也以為會拋出異常,崩潰,實際上在崩潰之前還有三次容錯機制進行處理,按照以下順序:

? ? ?Method Resolution

? ? ?Fast Forwarding

? ? ?Normal Forwarding

上圖最直接:


容錯處理的最后三步


Method Resolution

首先Objective-C在運行時調用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實現。如果你添加方法并返回YES,那系統在運行時就會重新啟動一次消息發送的過程。

舉一個簡單例子,定義一個類Message,它主要定義一個方法sendMessage,下面就是它的設計與實現:


@interface?Message?:?NSObject

-?(void)sendMessage:(NSString?*)word;

@end


@implementation?Message

-?(void)sendMessage:(NSString?*)word

{

NSLog(@"normal?way?:?send?message?=?%@",?word);

}

@end

如果我在viewDidLoad方法中創建Message對象并調用sendMessage方法:


-?(void)viewDidLoad?{

[super?viewDidLoad];

Message?*message?=?[Messagenew];

[message?sendMessage:@"Sam?Lau"];

}

控制臺會打印以下信息:

normal way : send message = Sam Lau

但現在我將原來sendMessage方法實現給注釋掉,覆蓋resolveInstanceMethod方法:


#pragma?mark?-?Method?Resolution

///?override?resolveInstanceMethod?or?resolveClassMethod?for?changing?sendMessage?method?implementation

+?(BOOL)resolveInstanceMethod:(SEL)sel

{

? ? if(sel?==?@selector(sendMessage:))?{

? ? ? ? class_addMethod([selfclass],?sel,?imp_implementationWithBlock(^(id?self,?NSString?*word)?{

? ? ? ? NSLog(@"method?resolution?way?:?send?message?=?%@",?word);

? ? ? ? }),"v@*");

? ? }

returnYES;

}

控制臺就會打印以下信息:

method resolution way : send message = Sam Lau

注意到上面代碼有這樣一個字符串"v@*,它表示方法的參數和返回值,詳情請參考Type Encodings

如果resolveInstanceMethod方法返回NO,運行時就跳轉到下一步:消息轉發(Message Forwarding)。

Fast Forwarding

如果目標對象實現- forwardingTargetForSelector:方法,系統就會在運行時調用這個方法,只要這個方法返回的不是nil或self,也會重啟消息發送的過程,把這消息轉發給其他對象來處理。否則,就會繼續Normal Fowarding。

繼續上面Message類的例子,將sendMessage和resolveInstanceMethod方法注釋掉,然后添加forwardingTargetForSelector方法的實現:


#pragma?mark?-?Fast?Forwarding

-?(id)forwardingTargetForSelector:(SEL)aSelector

{

? ? if(aSelector?==?@selector(sendMessage:))?{

? ? return[MessageForwardingnew];

? ? }

? ? return nil;

}

此時還缺一個轉發消息的類MessageForwarding,這個類的設計與實現如下:


@interface?MessageForwarding?:?NSObject

-?(void)sendMessage:(NSString?*)word;

@end


@implementation?MessageForwarding

-?(void)sendMessage:(NSString?*)word

{

NSLog(@"fast?forwarding?way?:?send?message?=?%@",?word);

}

@end

此時,控制臺會打印以下信息:

fast forwarding way : send message = Sam Lau

這里叫Fast,是因為這一步不會創建NSInvocation對象,但Normal Forwarding會創建它,所以相對于更快點。

Normal Forwarding

如果沒有使用Fast Forwarding來消息轉發,最后只有使用Normal Forwarding來進行消息轉發。它首先調用methodSignatureForSelector:方法來獲取函數的參數和返回值,如果返回為nil,程序會Crash掉,并拋出unrecognized selector sent to instance異常信息。如果返回一個函數簽名,系統就會創建一個NSInvocation對象并調用-forwardInvocation:方法。

繼續前面的例子,將forwardingTargetForSelector方法注釋掉,添加methodSignatureForSelector和forwardInvocation方法的實現:


#pragma?mark?-?Normal?Forwarding

-?(NSMethodSignature?*)methodSignatureForSelector:(SEL)aSelector

{

? ? NSMethodSignature?*methodSignature?=?[super?methodSignatureForSelector:aSelector];

? ? if(!methodSignature)?{

? ? ? ? methodSignature?=?[NSMethodSignature?signatureWithObjCTypes:"v@:*"];

? ? }

? ? return methodSignature;

}

-?(void)forwardInvocation:(NSInvocation?*)anInvocation

{

? ? MessageForwarding?*messageForwarding?=?[MessageForwardingnew];

? ? if([messageForwarding?respondsToSelector:anInvocation.selector])?{

? ? ? ? [anInvocation?invokeWithTarget:messageForwarding];

? ? }

}


-----------------------------------華麗的分割線-------------------------------------------


結束語: runtime是個好東西,雖然本人平時日常開發很少用到,但是了解其原理以及底層結構,會讓你對整個oc的運作有一個更好的理解


最后,本文章也是參考劉耀柱大神的文章寫出來的,更多關于runtime的干貨,在下面鏈接:


http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/6

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

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,746評論 0 9
  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的, 在面試過程中是經常會被問到的, ...
    made_China閱讀 1,215評論 0 7
  • runtime 運行時語言,實現Object-C的C語言庫,將OC轉換成C進行編譯的過渡者。 作為一門動態編程語言...
    夜雨聲煩_閱讀 554評論 0 0
  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的, 在面試過程中是經常會被問到的, ...
    SOI閱讀 21,837評論 3 63
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,578評論 33 466