消息转发流程

403 阅读2分钟

instrumentObjcMessageSends

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

objcMsgLogEnabledtrue,可以调用logMessageSend函数,打印整个方法调用流程。添加代码

extern void instrumentObjcMessageSends(BOOL flag);
instrumentObjcMessageSends(YES);
....
instrumentObjcMessageSends(NO);

进入tmp文件夹查看打印结果,

image.png

image.png 可以看到,除了动态方法决议的两个函数resolveClassMethodresolveInstanceMethod外,程序还执行了forwardingTargetForSelectormethodSignatureForSelectordoesNotRecognizeSelector函数。

forwardingTargetForSelector

在苹果文档里面查看forwardingTargetForSelector函数说明

Returns the object to which unrecognized messages should first be directed.

这个函数可以为方法指定另一个接收者,来接收该方法。
测试,新建类LKPersonLKStudent,在LKPerson中添加方法sayHello,在LKStudent中实现方法sayHello

@interface LKStudent : NSObject
@end

@implementation LKStudent
- (void)sayHello{
    NSLog(@"%s",__func__);
}
@end

@interface LKPerson : NSObject
- (void)sayHello;
@end

@implementation LKPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [LKStudent alloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LKPerson *person = [LKPerson alloc];
        [person sayHello];
    }
    return 0;
}

打印结果-[LKStudent sayHello]

程序并没有崩溃,并且打印方法[LKStudent sayHello]

methodSignatureForSelectorforwardInvocation

在苹果文档里面查看methodSignatureForSelector函数说明,

Returns an NSMethodSignature object that contains a description of the method identified by a given selector.

这个方法必须和forwardInvocation函数同时调用。
methodSignatureForSelector函数给方法返回一个签名,forwardInvocation函数来重新解读这个方法。
测试

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    LKStudent *p = [LKStudent alloc];
    anInvocation.target = p;
    anInvocation.selector = @selector(studentSayHello);
    [anInvocation invoke];
}

打印结果-[LKStudent studentSayHello]
  • return [NSMethodSignature signatureWithObjCTypes:"v@:"];给方法重新签名
  • anInvocation.target = p;重新指定方法接收者
  • anInvocation.selector = @selector(studentSayHello);重新指定方法SEL

doesNotRecognizeSelector

+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

NSObject’s implementation of forwardInvocation: simply invokes the doesNotRecognizeSelector: method; it doesn’t forward any messages. Thus, if you choose not to implement forwardInvocation:, sending unrecognized messages to objects will raise exceptions.

当经过慢速查找,快速查找,动态方法决议以及消息转发流程之后,仍然找到方法,会抛出异常,调用doesNotRecognizeSelector,打印错误日志。