objc_msgSend方法动态决议和消息转发

327 阅读4分钟

1.方法动态决议

1.1 forward_imp的源码实现查看

上一篇我们可以知道消息的发送最终转化为objc_msgSend函数,会在缓存和方法列表中查找imp,直到查找到父类为nil,走到objc源码中的lookUpImpOrForward方法中如下:

if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
      // No implementation found, and method resolver didn't help.
      // Use forwarding.
      imp = forward_imp;
      break;
 }

从源码中我们可以找到forwoar_imp的实现最后看到如下代码:

// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}

这里我们会看到一个常见的崩溃信息unrecognized selector; 当imp赋值为forward_imp后进入resolveMethod_locked方法: image.png 其中resolveInstanceMethodresolveClassMethod就是方法的动态决议;

1.2 方法的动态决议

1.2.1 实例方法的动态决议

resolveMethod_locked中,当cls不是元类是进入resolveInstanceMethod中,在resolveInstanceMethod方法源码中可以看到系统回去查找是否实现resolveInstanceMethod方法;

当类中,有实例方法test,没有实现,实现如下动态决议,就会调用method1方法,代码如下::

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s,%@", __func__, NSStringFromSelector(sel));
    if( sel == @selector(test) ) {
        IMP imp = class_getMethodImplementation(self.class, @selector(method1));
        class_addMethod(self.class, sel, imp, "v@:");
        return YES;
    }
    return  [super resolveInstanceMethod:sel];
}

- (void)method1 {
    NSLog(@"%s", __func__);
}

1.2.2 类方法的动态决议

当类中,有类方法test,没有实现,实现如下动态决议,就会调用method2方法,代码如下:

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s,%@", __func__, NSStringFromSelector(sel));
    if( sel == @selector(test1) ) {
        IMP imp = class_getMethodImplementation(self.class, @selector(method2));
        class_addMethod(objc_getMetaClass("YLPerson"), sel, imp, "v@:");
        return YES;
    }
    return  [super resolveClassMethod:sel];
}

- (void)method2 {
    NSLog(@"%s", __func__);
}

如下代码,没有实现method3方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s,%@", __func__, NSStringFromSelector(sel));
    IMP imp = class_getMethodImplementation(self.class, @selector(method1));
    class_addMethod(self.class, sel, imp, "v@:");
    return YES;
}

- (void)method1 {
    NSLog(@"%s", __func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s,%@", __func__, NSStringFromSelector(sel));
    IMP imp = class_getMethodImplementation(self.class, @selector(method3));
    class_addMethod(objc_getMetaClass("YLPerson"), sel, imp, "v@:");
    return YES;
}

将调用到method1方法中,因为class_getMethodImplementation(self.class, @selector(method3)),没有找到method3方法,将触发方法的动态决议,因为传入是当前类,会触发实例方法的动态决议resolveInstanceMethod

如下将self.class改为元类,没有实现method3方法:

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s,%@", __func__, NSStringFromSelector(sel));
    IMP imp = class_getMethodImplementation(objc_getMetaClass("YLPerson"), @selector(method3));
    class_addMethod(objc_getMetaClass("YLPerson"), sel, imp, "v@:");
    return YES;
}

将循环调用resolveClassMethod,因为class_getMethodImplementation触发了方法动态决议,并且传入的是元类,而在resolveMethod_locked方法中会判断是否是元类选择调用是类方法的动态决议还是实例方法的动态决议,而Class目前是元类。

2.消息转发

2.1.消息转发引出

lookUpImpOrForward方法中,当imp有值的时候,进入log_and_fill_cache方法中,最终可以看到根据objcMsgLogEnabled来判断是否向/tmp/msgSends中打印日志,而objcMsgLogEnabled只在instrumentObjcMessageSends函数中赋值,定义一个类调用方法,方法不去实现,如下:

image.png

前往tmp文件夹,查看日志如下: image.png 可以看到在动态决议以后,崩溃之前还调用了forwardingTargetForSelectormethodSignatureForSelector方法,这两个方法就是消息的转发。

消息的转发就是当在目前类没有找到这个方法的imp,可以将这个消息转发给其他类。

2.2.消息的快速转发

forwardingTargetForSelector就是消息的快速转发,可以如下实现:

-(id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));

    if (aSelector == @selector(method1)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果是类方法的消息转发是+(id)forwardingTargetForSelector:(SEL)aSelector类方法;

2.3.消息的慢速转发

methodSignatureForSelector是消息的慢速转发,当方法的快速转发没有实现,会走到消息的慢速转发,代码如下:

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {

    LGTeacher *t = [LGTeacher alloc];
    if ([self respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self];
    }else if ([t respondsToSelector:anInvocation.selector] ) {
        [anInvocation invokeWithTarget:t];
    }else {
        NSLog(@"%s-%@",__func__,NSStringFromSelector(anInvocation.selector));
    }
}

methodSignatureForSelectorforwardInvocation是搭配使用的,forwardInvocation中可以不做任何处理也不会崩溃。类方法的快速转发是+(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector+(void)forwardInvocation:(NSInvocation *)anInvocation

可以用来收集没有实现的方法.

3.方法动态决议为什么会被调用两次?

在崩溃的时候我们可以通过打印信息看到resolveInstanceMethod动态决议方法被调用了两次,第一次是objc_msgSend开始到lookUpImpOrForward,堆栈信息如下: image.png 第二次调用打印堆栈信息,可以看到是从慢速转发methodSignatureForSelector方法调用class_getInstanceMethod到lookUpImpOrForward,如下: image.png

总结

  1. objc_msgSend(receiver,sel,...),消息的接受者和方法名称,其它参数
  2. 消息的快速查找(从缓存中查找)
  3. _objc_msgSend_uncached方法
  4. 消息的慢速查找(从方法类别,父类的缓存、父类的方法列表中,直到父类为nil)
  5. 方法的动态协议(resolveInstanceMethod,resolveClassMethod)
  6. 消息的快速转发(forwardingTargetForSelector)
  7. 消息的慢速转发(methodSignatureForSelector,forwardInvocation)
  8. 异常抛出(doesNotRecognizeSelector)