iOS 消息转发

287 阅读3分钟
原文链接: www.jianshu.com

有时候我们常常看到一个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。

下面苹果官方文档参考 :developer.apple.com/documentati…

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: github.com/renmoqiqi/M…
参考链接:

github.com/bang590/JSP…

llyblog.com/2017/04/01/…

www.cocoachina.com/ios/2015060…

github.com/ChenYilong/…