1. 程式人生 > >OC學習Runtime之訊息傳遞,訊息轉發機制

OC學習Runtime之訊息傳遞,訊息轉發機制

堅持 成長 每日一篇

相關類和函式

介紹訊息傳送機制之前介紹一下會用到的幾個相關類和函式

NSMethodSignature(方法簽名)

方法簽名:用語記錄一個方法的引數和返回值型別的類。類似於objc_method_description結構體。方法簽名是用於初始化NSInvocation用的。

NSInvocation

用於記錄一個訊息(方法)的接收者(target)方法名(SEL)引數型別,引數等資訊,包含執行該訊息方法的類。
此類類似於結構體Method。
他提供了- (void)invokeWithTarget:(id)target;呼叫物件中的方法。類似於id method_invoke(id receiver, Method m, …) 函式。

注意:NSMethodSignature,NSInvocation一般我們都是用語訊息轉發時候用到。正常我們用的比較少。

objc_msgSend 函式

objc_msgSend函式負責像某物件傳送一個訊息。定義如下

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

在OC裡面我們呼叫物件方法[Receiver message]的這種模式,實際是通過呼叫objc_msgSend(Receiver,message,…)函式來找到方法的實現入口。objc_msgSend實現原理是通過物件對應的objc_object的ISA找到該類對應的objc_class結構體。通過依次遍歷objc_cache,objc_method_list裡面的方法找到方法實際入口,如果沒有找到,則跳到父類尋找,以此類推如果最終都沒有找到就會發生下面介紹的訊息轉發機制。

訊息轉發機制

Runtime中方法的動態繫結讓我們寫程式碼時更具靈活性,如我們可以把訊息轉發給我們想要的物件,或者隨意交換一個方法的實現等。不過靈活性的提 升也帶來了效能上的一些損耗。畢竟我們需要去查詢方法的實現,而不像函式呼叫來得那麼直接。當然,方法的快取一定程度上解決了這一問題。

特別是當我們需要在一個迴圈內頻繁地呼叫一個特定的方法時,通過這種直接呼叫IMP減少查詢過程的方式可以提高程式的效能。NSObject類提供了methodForSelector:方法,讓我們可以獲取到方法的指標,然後通過這個指標來呼叫實現程式碼。我們需要將methodForSelector:返回的指標轉換為合適的函式型別,函式引數和返回值都需要匹配上。

當我們像一個物件傳送訊息[Receiver message],Receiver沒有實現該訊息,即[Receiver respondsToSelector:SEL]返回為NO情況下,其實系統不會立刻出現cash,這時Runtime system會對message進行轉發。轉發之後,如果該訊息依然沒有被執行就會出現Cash!Runtime System為我們提供了三種解決這種給物件傳送沒有實現訊息方案。
訊息轉發機制基本上分為三個步驟:
1. 動態方法解析
2. 備用接收者
3. 完整轉發
我們可以通過控制這三個步驟其中一環來解決這一個問題

特別注意:如果是正常類的訊息,是不會走到這三個步驟的。所以走到這三個不步驟的前提條件已經確定該訊息為未知訊息

測試用例用到的原始碼
Boy.h


#import <Foundation/Foundation.h>

@interface Boy : NSObject
-(void)say:(NSString*)str girl:(NSString*)girl;
@end

Boy.m

#import "Boy.h"
#import "Girl.h"
@implementation Boy
-(void)say:(NSString*)str girl:(NSString*)girl
{
    NSLog(@"%@",str);
    NSLog(@"%@",girl);

}
@end

Girl.h

#import <Foundation/Foundation.h>

@interface Girl : NSObject
-(void)sayGirl:(NSString*)word;
@end

Girl.m

#import "Girl.h"
#import <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd)
{
    NSString *className = NSStringFromClass([self class]);
    NSString *selName = NSStringFromSelector(_cmd);
    NSLog(@"%@:不是%@的方法",className,selName);//給使用者警告但是不cash
}
@implementation Girl
-(void)sayGirl:(NSString*)word
{
    NSLog(@"I am a girl");
}
@end

動態方法解析

物件在接收到未知的訊息時,首先會呼叫所屬類的類方法+resolveInstanceMethod:(例項方法)或 者+resolveClassMethod:(類方法)。在這個方法中,我們有機會為該未知訊息新增一個”處理方法”“。不過使用該方法的前提是我們已經 實現了該”處理方法”,我們可以在執行時通過class_addMethod函式動態新增未知訊息到類裡面,讓原來沒有處理這個方法的類具有了處理這個方法的能力。

例項:

    Girl *girl = [[Girl alloc] init];
    [girl performSelector:@selector(sayGirl:) withObject:nil];
    [girl performSelector:@selector(checkBoy) withObject:nil];

輸出:

2015-09-22 15:14:58.619 Runtime[24078:1202259] I am a girl
2015-09-22 15:14:58.619 Runtime[24078:1202259] -[Girl checkBoy]: unrecognized selector sent to instance 0x100203cb0

可以看出系統cash一條unrecognized selector的異常。
現在我們在Girl.m實現+(BOOL)resolveInstanceMethod:(SEL)sel方法。類方法也類似。

+(BOOL)resolveInstanceMethod:(SEL)sel
{

    //從系統裡匹配SEL,如沒有就註冊SEL
    SEL systemSel = sel_registerName(sel_getName(sel));
    //把所有未知的SEL指向dynamicMethodIMP的實現,讓dynamicMethodIMP幫忙列印錯誤資訊,但是程式不會Cash
    class_addMethod(self,systemSel,(IMP)dynamicMethodIMP,"[email protected]:");
    return [super resolveClassMethod:systemSel];
}

再執行上面的例項程式碼時候就輸出正常,並切不會cash
輸出:

2015-09-22 15:17:25.639 Runtime[24223:1212399] I am a girl
2015-09-22 15:17:25.640 Runtime[24223:1212399] checkBoy:不是Girl的方法

對於dynamicMethodIMP裡面處理的事情可以是提醒,也可以把這條訊息轉發給其他物件等。

備用接收者

如果在動態方法解析無法處理訊息,則Runtime會繼續調以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果一個物件實現了這個方法,並返回一個非nil的物件,則返回的物件會作為訊息的新接收者,且訊息會被分發到這個物件。當然這個物件不能是self自身,否則就是出現無限迴圈。當然,如果我們沒有指定相應的物件來處理aSelector,則應該呼叫父類的實現來返回結果。
使用這個方法通常是在物件內部,可能還有一系列其它物件能處理該訊息,我們便可借這些物件來處理訊息並返回,這樣在物件外部看來,還是由該物件親自處理了這一訊息。
在Boy.m裡面新增如下程式碼

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [[Girl alloc] init];
}

例項:

Boy *boy = [[Boy alloc] init];
[boy performSelector:@selector(sayGirl:) withObject:nil];//發一條girl的訊息
[boy performSelector:@selector(checkBoy) withObject:nil];//發一條兩個物件都無法識別的訊息,由於girl可以接收任意訊息所以這裡也不會cash

輸出:

2015-09-22 15:26:21.974 Runtime[24746:1250012] I am a girl  //成功轉發了訊息
2015-09-22 15:26:21.974 Runtime[24746:1250012] checkBoy:不是Girl的方法 

完整訊息轉發

如果在上一步還不能處理未知訊息,則唯一能做的就是啟用完整的訊息轉發機制了。
此時會呼叫以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

由於NSInvocation的初始化需要有一個方法簽名NSMethodSignature,所以我們還需要實現下面的方法,該方法的返回值用於初始化NSInvocation的。

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector

所以我們在Boy.m裡面實現這兩個函式,在實現這兩個函式之前註釋掉上一步新增的

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [[Girl alloc] init];
}

因為如果在上一步中如果未知訊息被處理就不會走到這裡了,刪除完之後在Boy.m裡新增程式碼如下

//在這裡產生方法簽名,以確保NSInvocation能被轉發的Girl類執行,不然的話會出現錯誤
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([Girl instancesRespondToSelector:aSelector]) {
            signature = [Girl instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([Girl instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[[Girl alloc] init]];//將訊息轉發給Girl物件
    }
}

例項:

    [boy performSelector:@selector(sayGirl:) withObject:nil];
    [boy performSelector:@selector(checkBoy) withObject:nil];

輸出:

2015-09-23 09:31:50.388 Runtime[3583:151342] I am a girl
2015-09-23 09:31:50.388 Runtime[3583:151342] checkBoy:不是Girl的方法

輸出結果與備用接收者那一步一樣,表示我們轉發成功
NSObject的forwardInvocation:方法實現只是簡單呼叫了doesNotRecognizeSelector:方法,它不會轉發任何訊息。這樣,如果不在以上所述的三個步驟中處理未知訊息,則會引發一個異常。
從某種意義上來講,forwardInvocation:就像一個未知訊息的分發中心,將這些未知的訊息轉發給其它物件。或者也可以像一個運輸站一樣將所有未知訊息都發送給同一個接收物件。這取決於具體的實現。
訊息轉發與多重繼承
回過頭來看第二和第三步,通過這兩個方法我們可以允許一個物件與其它物件建立關係,以處理某些未知訊息,而表面上看仍然是該物件在處理訊息。通過這 種關係,我們可以模擬“多重繼承”的某些特性,讓物件可以“繼承”其它物件的特性來處理一些事情。不過,這兩者間有一個重要的區別:多重繼承將不同的功能 整合到一個物件中,它會讓物件變得過大,涉及的東西過多;而訊息轉發將功能分解到獨立的小的物件中,並通過某種方式將這些物件連線起來,並做相應的訊息轉 發。
不過訊息轉發雖然類似於繼承,但NSObject的一些方法還是能區分兩者。如respondsToSelector:和isKindOfClass:只能用於繼承體系,而不能用於轉發鏈。便如果我們想讓這種訊息轉發看起來像是繼承,則可以重寫這些方法,如以下程式碼所示:

- (BOOL)respondsToSelector:(SEL)aSelector   {
       if ( [super respondsToSelector:aSelector] )
                return YES;     
       else {
                 /* Here, test whether the aSelector message can
                  *            
                  * be forwarded to another object and whether that  
                  *            
                  * object can respond to it. Return YES if it can.  
                  */      
       }
       return NO;  
}

小結
在此,我們已經瞭解了Runtime中訊息傳送和轉發的基本機制。這也是Runtime的強大之處,通過它,我們可以為程式增加很多動態的行為,雖 然我們在實際開發中很少直接使用這些機制(如直接呼叫objc_msgSend),但瞭解它們有助於我們更多地去了解底層的實現。其實在實際的編碼過程中,我們也可以靈活地使用這些機制,去實現一些特殊的功能,如hook操作等。

相關推薦

OC學習Runtime訊息傳遞,訊息轉發機制

堅持 成長 每日一篇 相關類和函式 介紹訊息傳送機制之前介紹一下會用到的幾個相關類和函式 NSMethodSignature(方法簽名) 方法簽名:用語記錄一個方法的引數和返回值型別的類。類似於objc_method_description結構體。方

OC訊息傳送以及轉發機制

SEL:Objective-C在編譯時,會依據每一個方法的名字、引數序列,生成一個唯一的整型標識(Int型別的地址),這個標識就是SEL.本質上,SEL只是一個指向方法的指標. Method:Method = SEL + IMP + method_types,相當於在SEL和IMP之間建立

Android開發藝術探索》學習筆記Android的訊息機制.md

《Android開發藝術探索》學習筆記之Android的訊息機制 一、概述 1、Handler的主要作用是將某個任務切換到指定的執行緒中去執行 eg:子執行緒中無法更新UI,需切換到主執行緒 V

大資料學習筆記kafka----分散式訊息釋出/訂閱系統

一、kafka簡介 kafka是Linkedin於2012年12月份開源的訊息系統 kafka是一個分散式的,基於釋出/訂閱的訊息系統; kafka:一個佇列平臺,不僅支援離線,還支援線上 特點: --訊息持久化:通過O(1)的磁碟資料結構提供資料的持久化;針對磁碟

.NET學習手記:WPF--訊息框和對話方塊

1、訊息框: WPF的訊息框和和winform的引數不一樣: if (MessageBox.Show("內容", "標題", MessageBoxButton.YesNo, MessageBoxImage.Information) == MessageBoxResult.Ye

OC學習小結ios運行過程詳解

for cat 用戶 with res nbsp c學習 launch cati 1)ios核心類 UIView 視圖,屏幕上能看得見的東西都是視圖,例如:按鈕、文本標簽、和表格等 UIViewController:內部默認有個視圖(UIView),負責管理UIView的

OC學習--- property和 synthesize的使用

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---謂詞 NSPredicate

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---類的三大特性 封裝,繼承,多型

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---檔案的操作

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---協議的概念和用法

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---類的初始化方法和點語法的使用

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---通知 NSNotificationCenter

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

OC學習---歸檔和解擋

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Java學習筆記引用傳遞及其應用

介紹一下java裡面三種引用傳遞 Demo 1 public class ChapterFive { public static void main(String[] args) { Demo1 d1 = new Demo1(); // 例項化Demo1物件

OC學習集合物件

NSArray不可存放基本資料型別,只能存放類的例項,如果需要將基本資料型別、結構體放入到陣列的話,需要通過NSNumber、NSValue進行資料封裝,同時不能在NSArray中儲存nil NSString *str1 = @"zhangsan"; NSString *str2 = @"lis

OC學習---Foundation框架中的NSArray類和NSMutableArray類

在之前的一篇文章中介紹了Foundation框架中的NSString類和NSMutableString類:今天我們繼續來看一下Foundation框架中的NSArray類和NSMutableArray類,其實NSArray類和Java中的List差不多,算是一種資料結構,當然

OC學習---KVC和KVO操作

一、KVC操作OC中的KVC操作就和Java中使用反射機制去訪問類的private許可權的變數,很暴力的,這樣做就會破壞類的封裝性,本來類中的的private許可權就是不希望外界去訪問的,但是我們這樣去操作,就會反其道而行,但是我們有時候真的需要去這樣做,哎。所以說有些事不是

OC學習---類的三大特性(封裝,繼承,多型)

之前的一片文章介紹了OC中類的初始化方法和點語法的使用:http://blog.csdn.net/jiangwei0910410003/article/details/41683873,今天來繼續學習OC中的類的三大特性,我們在學習Java的時候都知道,類有三大特性:繼承,封

OC學習筆記OC物件的記憶體管理

一、為什麼要做記憶體管理        相對於現在動不動就上T的硬碟外存來說,計算機的記憶體雖然也在提升在還是太小了,而現在的應用軟體也十分吃記憶體,程式執行程序中如果不管理記憶體,如果有洩露,系統記憶體將會越用越小,對移動裝置來說更是如此。蘋果手機的記憶體只有那麼大,