消息发送及转发
消息发送及转发机制
  方法调用
    objc_msgSend(receiver, selector)或objc_msgSend(receiver, selector, arg1, arg2, ...)
    获取方法地址
    这个函数完成了动态绑定的所有事情：
1.首先它找到selector对应的方法实现。因为同一个方法可能在不同的类中有不同的实现，所以我们需要依赖于接收者的类来找到的确切的实现。
2.它调用方法实现，并将接收者对象及方法的所有参数传给它。
3.最后，它将实现返回的值作为它自己的返回值。
消息的关键在于我们前面章节讨论过的结构体objc_class，这个结构体有两个字段是我们在分发消息的关注的：
1.指向父类的指针
2.一个类的方法分发表，即methodLists。
当我们创建一个新对象时，先为其分配内存，并初始化其成员变量。其中isa指针也会被初始化，让对象可以访问类及类的继承体系。
当消息发送给一个对象时，objc_msgSend通过对象的isa指针获取到类的结构体，然后在方法分发表里面查找方法的selector。如果没有找到selector，则通过objc_msgSend结构体中的指向父类的指针找到其父类，并在父类的分发表里面查找方法的selector。依此，会一直沿着类的继承体系到达NSObject类。一旦定位到selector，函数会就获取到了实现的入口点，并传入相应的参数来执行方法的具体实现。如果最后没有定位到selector，则会走消息转发流程。
  消息转发
    动态方法解析
      +resolveInstanceMethod:(实例方法)
+resolveClassMethod:(类方法)
    备用接收者
      - (id)forwardingTargetForSelector:(SEL)aSelector;(实例方法)
+ (id)forwardingTargetForSelector:(SEL)aSelector;(类方法)
    完整转发
      消息签名
        - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;(实例方法)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;(类方法)
      转发调用
        - (void)forwardInvocation:(NSInvocation *)anInvocation;(实例方法)
+ (void)forwardInvocation:(NSInvocation *)anInvocation;(类方法)
      消息未识别
        - (void)doesNotRecognizeSelector:(SEL)aSelector;(实例方法)
+ (void)doesNotRecognizeSelector:(SEL)aSelector;(类方法)
Runtime中方法的动态绑定让我们写代码时更具灵活性，如我们可以把消息转发给我们想要的对象，或者随意交换一个方法的实现等。不过灵活性的提升也带来了性能上的一些损耗。毕竟我们需要去查找方法的实现，而不像函数调用来得那么直接。当然，方法的缓存一定程度上解决了这一问题。
我们上面提到过，如果想要避开这种动态绑定方式，我们可以获取方法实现的地址，然后像调用函数一样来直接调用它。特别是当我们需要在一个循环内频繁地调用一个特定的方法时，通过这种方式可以提高程序的性能。
NSObject类提供了methodForSelector:方法，让我们可以获取到方法的指针，然后通过这个指针来调用实现代码。我们需要将methodForSelector:返回的指针转换为合适的函数类型，函数参数和返回值都需要匹配上。
我们通过以下代码来看看methodForSelector:的使用：
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(test:)];
for (int i = 0; i < 100000; i++) {
setter(target, @selector(test:), YES);
}
这里需要注意的就是函数指针的前两个参数必须是id和SEL。
当然这种方式只适合于在类似于for循环这种情况下频繁调用同一方法，以提高性能的情况。另外，methodForSelector:是由Cocoa运行时提供的；它不是Objective-C语言的特性。
默认情况下，对象接收到未知的消息，会导致程序崩溃，通过控制台，我们可以看到以下异常信息：
-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception &#039;NSInvalidArgumentException&#039;, reason: &#039;-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940&#039;
这段异常信息实际上是由NSObject的”doesNotRecognizeSelector”方法抛出的。
对象在接收到未知的消息时，首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中，我们有机会为该未知消息新增一个”处理方法”。不过使用该方法的前提是我们已经实现了该”处理方法”，只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。
static void crashHandle(id self, SEL _cmd) {
NSLog(@"Crash 处理");
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:methodName]) {
Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(crashHandle));
Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(crashHandle));
const char *encoding = method_getTypeEncoding(predicateMethod);
class_addMethod(predicateMetaClass, sel, impletor, encoding);
return YES;
}
return [super resolveClassMethod:sel];
}
如果一个对象实现了这个方法，并返回一个非nil的结果，则这个对象会作为消息的新接收者，且消息会被分发到这个对象。当然这个对象不能是self自身，否则就是出现无限循环。当然，如果我们没有指定相应的对象来处理aSelector，则应该调用父类的实现来返回结果。
使用这个方法通常是在对象内部，可能还有一系列其它对象能处理该消息，我们便可借这些对象来处理消息并返回，这样在对象外部看来，还是由该对象亲自处理了这一消息。
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sleep"]) {
// 注意1: 也可在此转发实例方法
#if 0
// 让 MZTempObj 去执行 aSelector, 实现消息的转发
LaoWang *myobject = [[LaoWang alloc] init];
return myobject;
#endif
// 转发类方法对应返回类对象
return [LaoWang class];
}
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 找出对应的 aSelector 签名
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
// 注意2: 也可以在此获取实例方法的签名
#if 0
if (nil == signature) {
// 是否有 aSelector
if ([LaoWang instancesRespondToSelector:aSelector]) {
signature = [LaoWang instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
#endif
if (nil == signature) {
// 是否有 aSelector
if ([LaoWang respondsToSelector:aSelector]) {
//methodSignatureForSelector 可以获取类方法和实例方法的签名
//instanceMethodSignatureForSelector只能获取实例方法的签名
signature = [LaoWang methodSignatureForSelector:aSelector];
}
}
return signature;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
// 注意3: 也可以调用实例方法
#if 0
if ([LaoWang instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[LaoWang alloc] init]];
}
else {
[super forwardInvocation:anInvocation];
}
return;
#endif
if ([LaoWang respondsToSelector:anInvocation.selector]) {
// 这里转发的是 LaoWang Class, 不是对象
[anInvocation invokeWithTarget:[LaoWang class]];
}
else {
[super forwardInvocation:anInvocation];
}
}
+ (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"---veryitman--- 5--- +doesNotRecognizeSelector: %@", NSStringFromSelector(aSelector));
}
在实现转发调用之前必须重写此方法，消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法，为给定的selector提供一个合适的方法签名。
运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象，把与尚未处理的消息有关的全部细节都封装在anInvocation中，包括selector，目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。
forwardInvocation:方法的实现有两个任务：
1.定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。
2.使用anInvocation作为参数，将消息发送到选中的对象。anInvocation将会保留调用结果，运行时系统会提取这一结果并将其发送到消息的原始发送者。
不过，在这个方法中我们可以实现一些更复杂的功能，我们可以对消息的内容进行修改，比如追回一个参数等，然后再去触发消息。另外，若发现某个消息不应由本类处理，则应调用父类的同名方法，以便继承体系中的每个类都有机会处理此调用请求。