平时我们只知道调用方法,其实内部的实现原理并不是很了解,方法的调用要用就是要用到接下来要讲的消息转发机制。在这里我们可以利用消息转发机制实现方法的动态添加。
注:所有的方法调用均采用下面的形式调用。
- (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
#pragma clang diagnostic ignored "-Wincomplete-implementation"
三、方法不声明,直接将方法替换。
(1).h文件中不做任何操作
@interface Dog : NSObject #pragma mark --- 方法替换不声明 @end
@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