runtime是oc中一個比較底層的純c語言庫,包含了很多底層c語言的api
runtime其實和我們編程密切相關,平時編寫的oc代碼中,在程序運行時,其實最終都是轉成runtime的c語言代碼
可以這么理解,oc的底層實際上是在調用一個個c函數,如何找到這些c函數的工作,就是由runtime去負責了
要理解runtime,首先要理解類的結構
類的本質是什么? 實際上就是一個類似結構體的玩意,那么類里面有什么內容呢,以下一張圖就能夠很清晰的理解
isa就是“is a”,對于所有繼承了NSObject的類其對象也都有一個isa指針。這個isa指針指向關于這個對象所屬的類的定義。?
任何直接或間接繼承了NSObject的類,它的實例對象(instacne objec)中都有一個isa指針,指向它的類對象(class object)。這個類對象(class object)中存儲了關于這個實例對象(instace object)所屬的類的定義的一切:包括變量,方法,遵守的協議等等。
以下這圖可以很清楚的表述上面的關系
簡單來說,一個類的對象的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里面又有什么內容呢?見下圖
methodlists(方法列表)
? ? ? 同ivars一樣理解,實際上這兩個結構體幾乎是一樣的,也是一個objc_method_list 的結構體, 也是一個鏈表,存儲多個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(方法緩存池)
? ? ? ? ? 二話不說,先上圖
? ? ? ? ? ?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