Runtime系列二 消息机制

2018/9/1 posted in  iOS基础概念

前面讨论了Runtime中对类和对象的处理以及对成员变量与属性的处理。本文将讨论Runtime里的消息机制。

一:方法调用的流程

在Objective-C中,消息直到运行时才绑定到指定的方法实现上。编译器会将消息表达式转换成一个消息函数的调用

 RuntimePerson *p = objc_msgSend([RuntimePerson class], @selector(alloc));
    p = objc_msgSend(p, @selector(init));
    [p eat];
    objc_msgSend(p, sel_registerName("eatFoot:"),@"汉堡");
//相应的底层实现(这是上面代码经过编译后)
Class pClass = objc_getClass("RuntimePerson");
RuntimePerson *pp  = objc_msgSend(pClass,sel_registerName("alloc"));
pp = objc_msgSend(pp, sel_registerName("init"));  
[pp eat];

objc_msgSend(receiver, selector)
objc_msgSend(receiver, selector, arg1, arg2, ...)

二 :消息的转发

当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象无法接收指定消息时,就会启动所谓”消息转发(message forwarding)“机制
消息转发机制基本上分为三个步骤:
动态方法解析
备用接收者
完整转发

1. 动态方法解析

对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:

```
+ (BOOL)resolveInstanceMethod:(SEL)sel{
class_addMethod(self, sel, (IMP)hh, "v@:@");

return [super resolveInstanceMethod:sel];
}

void hh(id obj,SEL sel,NSString *objc){
NSLog(@"我来了%@,%@,%@",obj,sel,objc);
}
```

2. 备用接收者

如果在上一步无法处理消息,则Runtime会继续调以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象

3. 完整转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

还有一个很重要的问题,我们必须重写以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }
}