天天看點

Runtime之消息轉發

目錄

消息轉發的流程圖:

1.動态方法解析

2.快速轉發

3.慢速轉發 

demo位址

   在OC中的執行個體對象調用一個方法稱作

發送消息

當向某個對象發送一條消息時,若該對象的方法清單以及它相應繼承鍊上的方法清單都無法找該消息的方法實作時,則會觸發消息轉發機制。

   比如有如下代碼:

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

- (void)sendMessage:(NSString*)message;

@end

NS_ASSUME_NONNULL_END
           
Person *p = [[Person alloc]init];
[p sendMessage:@"hello"];
           

.h檔案裡面聲明了一個方法,.m裡面沒有實作這個方法,而Person調用了這個沒有實作的方法,就會觸發消息轉發機制。

Person對象調用sendMessage:方法,OC的底層實作其實是下面這樣的

objc_msgSend([Person new],@selector(sendMessage:),@"hello");

           

沒有實作的方法,我們調用了,消息轉發機制就好比我們平時遇到不認識的字去查字典一樣。sendMessage:方法去哪裡找,objc_msgSend 去[Person new] 這本字典裡面去找,偏旁部首就是@selector這個方法編号,去查找@"hello"這個字。

我們看看isa指針的查找過程:

Runtime之消息轉發

通過目前對象的isa 指針找到class,在方法清單裡面查找方法,如果有就直接實作,如果目前清單裡面沒有,就沿着繼承樹去找,通過方法編号和IMP的映射關系,找到方法實作;如果都沒有,這個時候就進入消息轉發機制。

消息轉發的流程圖:

Runtime之消息轉發

消息轉發機制的流程

1.動态方法解析

第一步:對象在收到無法解讀的消息後,首先會調用+(BOOL)resolveInstanceMethod:(SEL)sel

或者+ (BOOL)resolveClassMethod:(SEL)sel, 詢問是否有動态添加方法來進行處理,處理執行個體如下

+(BOOL)resolveInstanceMethod:(SEL)sel {
	// 方法比對
	NSString *methodName = NSStringFromSelector(sel);
	if ([methodName isEqualToString:@"sendMessage:"]) {
		return class_addMethod(self, sel, (IMP)sendMessage, "v@:@");
	}
	return [super resolveInstanceMethod:sel];//如果沒有就走繼承樹方法
}
           

 動态方法解析,就是添加一個處理方法

void sendMessage(id self,SEL _cmd,NSString* message) {
	NSLog(@"---%@",message);
}

           

v@:@ 這個的意思是 v 表示傳回值是void類型,上面方法的參數id self,id類型用@表示,SEL 表示方法編号,用冒号:表示,我們自己傳的參數NSString類型用@表示。(id self, SEL _cmd, 是預設的參數)

運作之後我們看到列印了hello這個消息結果

Runtime之消息轉發

2.快速轉發

如果運作時在消息轉發的第一步中未找到所調用方法的實作,那麼目前接收者還有第二次機會進行處理。這時運作期系統會調用 -(id)forwardingTargetForSelector:(SEL)aSelector 方法,該方法可以傳回一個能處理的對象,運作時系統會根據傳回的對象進行查找,若找到則跳轉到相應方法的實作,則消息轉發結束。

快速轉發說白了就是,找一個接受者

-(id)forwardingTargetForSelector:(SEL)aSelector {

	NSString *methodName = NSStringFromSelector(aSelector);
			if ([methodName isEqualToString:@"sendMessage:"]) {
		return [SpareWheel new];//備用接受者
	}
	return [super forwardingTargetForSelector:aSelector];//走繼承樹方法
}
           

我們找的接受者的.m裡面已經實作了sendMessage:方法

#import "SpareWheel.h"

@implementation SpareWheel

- (void)sendMessage:(NSString*)message {
	
	NSLog(@"---SpareWheel say %@",message);

}
@end
           

運作結果如下:

Runtime之消息轉發

3.慢速轉發 

  1. 方法簽名 
  2. 消息轉發
#pragma mark - 方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

	NSString *methodName = NSStringFromSelector(aSelector);
	if ([methodName isEqualToString:@"sendMessage:"]) {
		return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
	}
	return [super methodSignatureForSelector:aSelector];
}
           

上面的方法簽名的資訊會儲存在NSInvocation中,通過 SEL sel = [anInvocation selector] 擷取方法編号

//這個方法是找一個 處理者
-(void)forwardInvocation:(NSInvocation *)anInvocation {
//消息轉發
	SEL sel = [anInvocation selector];
	SpareWheel *tempObjc = [SpareWheel new];// 找一個處理者
	if ([tempObjc respondsToSelector:sel]) {
// 如果這個處理者響應了這個方法,就指定它作為目标對象,處理目前的方法
		[anInvocation invokeWithTarget:tempObjc];
	}else{
		[super forwardInvocation:anInvocation];
	}
}
           

運作結果如下:

Runtime之消息轉發

慢速轉發運作結果

如果以上方法都沒有實作,會執行-(void)doesNotRecognizeSelector:(SEL)aSelector 這個方法,為了防止程式崩潰,可以這麼寫,增加程式的健壯性。

//找不到方法就執行這個
-(void)doesNotRecognizeSelector:(SEL)aSelector {
	NSLog(@"找不到方法");
}
           
Runtime之消息轉發

demo位址

本文消息轉發demo位址

繼續閱讀