RunTime机制详解(四)

平时我们只知道调用方法,其实内部的实现原理并不是很了解,方法的调用要用就是要用到接下来要讲的消息转发机制。在这里我们可以利用消息转发机制实现方法的动态添加。

注:所有的方法调用均采用下面的形式调用。

- (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


发布了64 篇原创文章 · 获赞 10 · 访问量 10万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章