runtime原理分析

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

runtime其實和我們編程密切相關(guān),平時編寫的oc代碼中,在程序運(yùn)行時,其實最終都是轉(zhuǎn)成runtime的c語言代碼


可以這么理解,oc的底層實際上是在調(diào)用一個個c函數(shù),如何找到這些c函數(shù)的工作,就是由runtime去負(fù)責(zé)了


要理解runtime,首先要理解類的結(jié)構(gòu)


類的本質(zhì)是什么? 實際上就是一個類似結(jié)構(gòu)體的玩意,那么類里面有什么內(nèi)容呢,以下一張圖就能夠很清晰的理解


一個Person類的內(nèi)部構(gòu)造

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

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

以下這圖可以很清楚的表述上面的關(guān)系

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

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


搞清楚對象和類的這層關(guān)系之后,要理解起來就輕松多了,你可以理解成isa 和 super class 就是一個門牌號或者路標(biāo),runtime就是根據(jù)這些路標(biāo)去找到相應(yīng)的靜態(tài)方法和變量的。

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

搞清楚指針之后,之后就看重點(diǎn)了

ivars(成員變量列表)

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

? ? ? ? ?簡單粗暴一點(diǎn)理解,ivar 就是成員變量

objc_ivar里面又有什么內(nèi)容呢?見下圖


objc_ivar結(jié)構(gòu)


methodlists(方法列表)

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

我們也可以來一張圖來展示objc_method的風(fēng)采


objc_method

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

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

舉個栗子方便理解:

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

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


Cache(方法緩存池)

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


objc_cache

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


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


消息發(fā)送

?當(dāng)調(diào)用一個方法時[a sayHello],其實是被編譯器轉(zhuǎn)化為

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

1.根據(jù)a實例對象的isa找到其對應(yīng)的類對象

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

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

4.一旦找到了,則執(zhí)行IMP的實現(xiàn)


容錯機(jī)制

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

? ? ?Method Resolution

? ? ?Fast Forwarding

? ? ?Normal Forwarding

上圖最直接:


容錯處理的最后三步


Method Resolution

首先Objective-C在運(yùn)行時調(diào)用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實現(xiàn)。如果你添加方法并返回YES,那系統(tǒng)在運(yùn)行時就會重新啟動一次消息發(fā)送的過程。

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


@interface?Message?:?NSObject

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

@end


@implementation?Message

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

{

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

}

@end

如果我在viewDidLoad方法中創(chuàng)建Message對象并調(diào)用sendMessage方法:


-?(void)viewDidLoad?{

[super?viewDidLoad];

Message?*message?=?[Messagenew];

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

}

控制臺會打印以下信息:

normal way : send message = Sam Lau

但現(xiàn)在我將原來sendMessage方法實現(xiàn)給注釋掉,覆蓋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@*,它表示方法的參數(shù)和返回值,詳情請參考Type Encodings

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

Fast Forwarding

如果目標(biāo)對象實現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會在運(yùn)行時調(diào)用這個方法,只要這個方法返回的不是nil或self,也會重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對象來處理。否則,就會繼續(xù)Normal Fowarding。

繼續(xù)上面Message類的例子,將sendMessage和resolveInstanceMethod方法注釋掉,然后添加forwardingTargetForSelector方法的實現(xiàn):


#pragma?mark?-?Fast?Forwarding

-?(id)forwardingTargetForSelector:(SEL)aSelector

{

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

? ? return[MessageForwardingnew];

? ? }

? ? return nil;

}

此時還缺一個轉(zhuǎn)發(fā)消息的類MessageForwarding,這個類的設(shè)計與實現(xiàn)如下:


@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,是因為這一步不會創(chuàng)建NSInvocation對象,但Normal Forwarding會創(chuàng)建它,所以相對于更快點(diǎn)。

Normal Forwarding

如果沒有使用Fast Forwarding來消息轉(zhuǎn)發(fā),最后只有使用Normal Forwarding來進(jìn)行消息轉(zhuǎn)發(fā)。它首先調(diào)用methodSignatureForSelector:方法來獲取函數(shù)的參數(shù)和返回值,如果返回為nil,程序會Crash掉,并拋出unrecognized selector sent to instance異常信息。如果返回一個函數(shù)簽名,系統(tǒng)就會創(chuàng)建一個NSInvocation對象并調(diào)用-forwardInvocation:方法。

繼續(xù)前面的例子,將forwardingTargetForSelector方法注釋掉,添加methodSignatureForSelector和forwardInvocation方法的實現(xiàn):


#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];

? ? }

}


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


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


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


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

最后編輯于
?著作權(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,058評論 0 9
  • runtime 和 runloop 作為一個程序員進(jìn)階是必須的,也是非常重要的, 在面試過程中是經(jīng)常會被問到的, ...
    made_China閱讀 1,273評論 0 7
  • runtime 運(yùn)行時語言,實現(xiàn)Object-C的C語言庫,將OC轉(zhuǎn)換成C進(jìn)行編譯的過渡者。 作為一門動態(tài)編程語言...
    夜雨聲煩_閱讀 627評論 0 0
  • runtime 和 runloop 作為一個程序員進(jìn)階是必須的,也是非常重要的, 在面試過程中是經(jīng)常會被問到的, ...
    SOI閱讀 22,025評論 3 63
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,892評論 33 466

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