iOS-消息转发机制

先从一个经典的报错开始。

是的,就是方法实现找不到,unrecognized selector sent to instance xxx。selector可以简单理解为字符串,也就是方法名。

方法调用分为三个过程:
1.消息发送
2.动态方法解析
3.消息转发

消息发送

当一个实例对象调用一个对象方法时(类对象调取类方法也同样适用),其实就是发送消息(这个大家都知道)。首先会用selector去类对象方法缓存中查找方法实现,如果找不到就会到类对象中的方法列表中查找方法实现(类方法是去元类对象中查找类方法列表),还是找不到就会用super指针去父类的方法列表中查找,一直找到根类也就是NSObject中的方法列表。其中某个过程找到了方法实现后,先会将方法实现的地址缓存在实例对象所在的类对象的方法缓存中,然后调用方法实现。这就是第一阶段,消息发送阶段。

动态方法解析

假如一直找到了NSObject也没有找到方法实现怎么办呢?就会来到第二阶段,动态方法解析,调用下面的两个方法

可以在重写这两个方法,动态的添加方法。第一个方法添加类方法,第二个添加实例方法。可以像下面这样

void method(SEL self, IMP _cmd, NSDictionary *userInfo) {
    NSLog(@"动态添加的方法");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(jumpToSecondVcWithUserInfo:)) {
        // 动态添加test方法的实现
        class_addMethod(self, sel, (IMP)method, "v24@0:8@16");

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

控制台就会输出

其中v24@0:8@16为类型编码(Type Encodings)这里不再累述,不了解的可以百度。

消息转发

如果还没有实现动态方法解析,这里就会来到消息转发机制。实际上就是下面的几个方法

具体的流程是什么样的呢?
1.首先会调用forwardingTarget,为没有找到方法实现的selector转发给另一个target。

@interface SQJump2 : NSObject

- (void)jumpToSecondVcWithUserInfo:(NSDictionary *)userInfo;

@end

@implementation SQJump2

- (void)jumpToSecondVcWithUserInfo:(NSDictionary *)userInfo {
    NSLog(@"消息转发第一步");
}

@end

@implementation SQJump

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(jumpToSecondVcWithUserInfo:)) {
        return SQJump2.new;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

这样就会由SQJump2的实例对象去响应这个selector。这里响应的是对象方法,但是类方法怎么办呢?NSObject头文件里面没有找到加号方法的forwardingTarget啊。了解消息发送机制就会知道,把

- (id)forwardingTargetForSelector:(SEL)aSelector;

改为

+ (id)forwardingTargetForSelector:(SEL)aSelector;

就可以了,系统会自动调用的。这样就可以转发类方法了。
这个方法也没有实现呢?
2.调用methodSignatureForSelector方法,返回一个方法签名(NSMethodSignature)。何为方法签名,简单说就是方法名、返回值、参数等信息用来区分方法的。
可以这样实现

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(jumpToSecondVcWithUserInfo:)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

然后就会走下一步
3.调用forwardInvocation。NSInvocation就是一个封装了方法调用的类。在这一步中,会拿到上一步中方法签名封装的invocation。
可以在这一步直接写入方法实现,例如:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"消息转发最后一步");
}

也可以交给另一个target去实现方法,例如:

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation invokeWithTarget:SQJump2.new];
}

想怎么整,全凭你的心情决定。

开头的那个奔溃

如果上面这么多的机会你一个都没有抓住,很抱歉,系统会自动调用

- (void)doesNotRecognizeSelector:(SEL)aSelector;

程序会出现异常,然后退出。

用处

说了这么多,消息转发机制有什么用呢?
平时的开发中基本上大多数人都没有接触过,但是不代表它不重要。我了解的用处大概下面几种:
1.动态添加方法,JSPatch就是基于这个区实现添加方法的。
2.模拟多继承,OC中是没有多继承概念的,用消息转发可以模拟这一过程。类似于鸭子对象。
3.减少线上奔溃率。unrecognized selector sent to instance xxx一定在线上奔溃列表中占有一席之地,如果我们将经常容易找不到的方法利用消息转发,可以极大的降低奔溃率。例如



之类的报错,可以用消息转发来消除奔溃。


好了,一篇大水文写完了。为了写这一篇文章,嗓子都哑了!


你可能感兴趣的:(iOS-消息转发机制)