iOS八股文(七)objc_msgSend之动态解析和消息转发

880 阅读3分钟

上一篇中,我们了解了objc_msgSend过程中的消息查找的流程,其中有个查找方法的函数名叫lookUpImpOrForward,字面翻译查找方法或者转发。那么本文就来记录下objc_msgSend的其他两个过程。

image.png

resolveMethod动态解析(动态决意)

objc4源码中的lookUpImpOrForward,有这么一段代码。

image.png

注释: 没有找到方法实现,尝试一次方法解析。

继续看里面resolveMethod_locked的实现。

image.png 这里对cls进行了是否是元类的判断,如果是元类,说明是类方法的调用,则调用resolveClassMethod。如果是类,说明是对象方法调用,则调用resolveInstanceMethod

image.png

其中resolveClassMethod resolveInstanceMethod 是通过msgSend去调用类方法resolveClassMethod: 或者resolveClassMethod:。其中resolveCla ssMethod中cls本来是元类,通过getMaybeUnrealizedNonMetaClass来获取元类对应的类对象,然后再对类对象发送消息(相当于调用类方法)。

image.png

resolveClassMethod: resolveClassMethod:的实现就交给了开发者,开发者可以再这里动态添加方法实现。

// 第一根稻草,使用动态解析,动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(methodOne)) {
        Method methodNormal = class_getInstanceMethod(self, @selector(methodNormal));
        class_addMethod(self,
                        @selector(methodOne),
                        method_getImplementation(methodNormal),
                        method_getTypeEncoding(methodNormal));
        return YES
    }
    return [super resolveInstanceMethod:sel];
}

这样methodOne的调用就可以使用methodNormal的实现了。

消息转发

如果动态解析(动态决意)阶段还是没有对应的方法,那么就会来到消息转发阶段。消息转发阶段分为两部分,替换消息接收者阶段,也有叫快速转发,和完全消息转发阶段。

消息接收者替换

实现forwardingTargetForSelector方法,并返回替换的对象。

//第二根稻草,使用快速消息转发,找其他对象来实现方法

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(methodTwo)) {
        return self.forwardObject;
    }
    return nil;
}

这时候就会去_frowardObject里面去找methodTwo了。_frowardObject的类实现如下:

@implementation OSMsgSendForwardObject
//从消息转发而来
- (void)methodTwo {
    NSLog(@"%s__ %@",__FUNCTION__,[self class]);
}
@end
完全消息转发

如果forwardingTargetForSelector方法返回的是nil,那么我们还有最后一根稻草可以抓住-完全消息转发。相比于快速抓发,不经可以替换消息接受者,还能替换方法。

//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(methodThree)) {
//        NSMethodSignature *sig = [NSMethodSignature methodSignatureForSelector:aSelector];
        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sig;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.selector = @selector(methodNormal);
    anInvocation.target = self.forwardObject;
    [anInvocation invoke];
}

这里有两个类,NSMethodSignatureNSInvocation。其中NSMethodSignature是方法签名,可以通过方法的字符来实例化。NSInvocation是方法调用实体,其中有target和selector和参数构成。详细可以看这里

通过实现这两个方法就可以实现消息的完全转发。

总结与思考

image.png 在消息发送的时候,如果在cache方法列表中都没有找到方法实现,会来到动态解析阶段,通过调用resolveInstanseMethodresolveClassMethod,来动态的解析方法的实现。如果在方法里返回NO,则会进入消息转发阶段,通过frowardingTargetSelector,可以将消息转发到能处理消息的实例里面。如果没有进行快速转发,可以methodSignatureForSeletorfrowardInvocation 来进行完全消息转发。

那么问题来了,苹果为什么要设计这些阶段,难到真的是怕开发者忘实现方法吗?

首先,确实动态解析,快速转发和完全转发都能做到防止程序unrecognized selector sent to instance 类型的crash,这三种方式也被称做消息发送的三根救命稻草。。这些阶段的前提都是在没有找到方法实现,设计这些并不会影响消息发送的效率。而每根稻草实际的实现原理是不同的,动态解析是动态的去添加方法实现,消息转发是让一个替代者来实现。

参考链接