iOS 消息转发

有时候我们常常看到一个cash 信息,意思是这个对象不存在这个方法,你向这个对象发送消息就会crash。

我们除了用respondsToSelector 这个方法来判断这个对象是否响应这个方法,还可以用消息转发来解决。

在系统抛出异常的时候你有还有三次机会来处理这个crash

第一次机会

让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步。主要是下面两个方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel

+ (BOOL)resolveClassMethod:(SEL)sel

在这个类的实现文件动态添加一个这个方法并重写上述方法来解决crash

void myMethod(id self, SEL _cmd) {

    NSLog(@"%@ %s",self,sel_getName(_cmd));

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wundeclared-selector"

    if (sel == @selector(run)) {

#pragma clang diagnostic pop

        class_addMethod([self class],sel,(IMP)myMethod,"v@:");

        return YES;

    }else {

        return [super resolveInstanceMethod:sel];

    }

}

第二次机会

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。

创建一个新的类 ,然后把这个消息,转发到新类上实现。

- (id)forwardingTargetForSelector:(SEL)aSelector {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wundeclared-selector"

    if (aSelector == @selector(run)) {

#pragma clang diagnostic pop

        return [TestTowClass new];

    }else{

        return [super forwardingTargetForSelector:aSelector];

    }

}

第三次机会

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加灵活,forwardingTargetForSelector: 只能固定的转发到一个对象;forwardInvocation: 可以让我们转发到多个对象中去。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wundeclared-selector"

    if (aSelector == @selector(run)) {

#pragma clang diagnostic pop

        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];

    }

    return [super methodSignatureForSelector:aSelector];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    TestTowClass *towClass = [TestTowClass new];

    TestThreeClass *threeClass = [TestThreeClass new];

    if ([towClass respondsToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:towClass];

    }

    if ([threeClass respondsToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:threeClass];

    }

}

注意:

关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。

下面苹果官方文档参考 :https://developer.apple.com/documentation/foundation/nsmethodsignature?language=objc

For example, the NSString instance method containsString: has a method signature with the following arguments:

@encode(BOOL) (c) for the return type

@encode(id) (@) for the receiver (self)

@encode(SEL) (:) for the selector (_cmd)

@encode(NSString *) (@) for the first explicit argument

Demo: https://github.com/renmoqiqi/MessageForwarding
参考链接:

https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3

http://llyblog.com/2017/04/01/iOS-Runtime-%E5%8A%A8%E6%80%81%E6%B6%88%E6%81%AF%E8%A7%A3%E6%9E%90%E5%92%8C%E6%B6%88%E6%81%AF%E8%BD%AC%E5%8F%91%E6%9C%BA%E5%88%B6/

http://www.cocoachina.com/ios/20150604/12013.html

https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8A%EF%BC%89.md#18-%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BC%9A%E6%8A%A5unrecognized-selector%E7%9A%84%E5%BC%82%E5%B8%B8

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