平時我們只知道調用方法,其實內部的實現原理並不是很瞭解,方法的調用要用就是要用到接下來要講的消息轉發機制。在這裏我們可以利用消息轉發機制實現方法的動態添加。
注:所有的方法調用均採用下面的形式調用。
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
一、消息轉發機制的流程圖。(圖片來源於網絡)
二、只申明方法,而不實現方法。
(1).h文件方法聲明
@interface Cat : NSObject
#pragma mark --- 只聲明不實現
- (void)eat;
@end
(2).m文件中處理
#import "Cat.h"
#import <objc/runtime.h>
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation Cat
//第一步:實現此方法,在調用對象的某方法找不到時,會先調用此方法,允許
//我們動態添加方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
class_addMethod(self, sel, (IMP)addEat, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//下面的方法是我們動態添加的
void addEat(id self,SEL cmd){
NSLog(@"%@ is eating",self);
}
@end
(3)當我們申明瞭方法而沒有實現該方法是,系統就會有警告,消除這個警告也很簡單,一句代碼就搞定。
#pragma clang diagnostic ignored "-Wincomplete-implementation"
三、方法不聲明,直接將方法替換。
(1).h文件中不做任何操作
@interface Dog : NSObject
#pragma mark --- 方法替換不聲明
@end
(2)所有處理都在.m文件中。
@implementation Dog
//1、在沒有找到方法是,會先調用此方法,可用於動態添加方法
//不需要動態添加
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
//2、上一步返回NO,就會進入這一步,用於指定備選響應次SEL的對象
//千萬不能返回self,否則會進入死循環
//因爲自己沒有實現這個方法纔會進入這一流程,因此成爲死循環
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
//3、指定方法簽名,若返回nil,則不會進入下一步,而是無法處理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//當我們實現了此方法,-doesNotRecognizeSelector:不會再被調用
//如果要測試找不到方法,可以註釋到這一個方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//我們還可以改變方法選擇器
[anInvocation setSelector:@selector(jump)];
//改變方法選擇器後,還需要指定是哪一個對象的方法
[anInvocation invokeWithTarget:self];
}
// 注意:兩個方法只能實現一個
//- (void)eat{
// NSLog(@"eat");
//}
- (void)jump{
NSLog(@"方法改變");
}
@end
四、私有方法的動態添加。
(1)在.h文件中不做任何操作
@interface Pig : NSObject
#pragma mark --- 不聲明-eat方法,但在內部有實現
@end
(2)所以的操作都在.m文件中
#import "Pig.h"
#import "Cat.h"
@implementation Pig
//第一步,我們不動態添加方法,返回NO
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return NO;
}
//第二步,備選提供響應Aseletor的對象,我們不備選,因此設置爲nil,就會進入第三步
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
//第三步,先返回方法選擇器,如果放回nil,則表示無法處理信息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"] ) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//第四步,第三步只有返回了方法簽名,纔會進入這一步,這一步用戶調用方法
//改變調用對象等
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//我們改變調用對象爲dog
[anInvocation invokeWithTarget:self];
// [anInvocation invokeWithTarget:[Cat new]];
// target可以別的類的對象,但那個類的實現了eat方法
// - (void)invokeWithTarget:(id)target;
}
- (void)eat{
NSLog(@"%@ is eating",self);
}
@end
代碼傳送門:https://github.com/fuzongjian/RuntimeStudy.git