iOS11 消息转发

527 阅读1分钟

日志辅助

  • 通过lookUpImpOrForward --> log_and_fill_cache --> logMessageSend进入logMessageSend 看到源码的实现
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;
}
  • /tmp/msgSends是日志保存的沙盒路径,开启以后直接到沙盒路径下就能获取文件。默认的objcMsgLogEnabled = false 所以要找到赋值的地方
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;
}
  • 通过instrumentObjcMessageSendsobjcMsgLogEnabled赋值,所以在需要日志信息的地方声明instrumentObjcMessageSendsextern void instrumentObjcMessageSends(BOOL flag);
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        NBPerson *nb = [NBPerson alloc];
        instrumentObjcMessageSends(YES);
        [nb sayHello];
        instrumentObjcMessageSends(NO);

    }
    return 0;
}
  • 这个时候我们发现报错而且查找文件里面没内容

D056234B-D11E-40B2-B932-FB3A6C545286.png

  • 遇到问题不要慌

32EB63A0-D39C-4124-9F08-8BA81F487272.png

DE57784C-40FC-4D79-B3A5-55C380348D05.png

消息转发

  • 消息发送在经过动态方法决议仍然没有查找到正真的方法实现,此时动态方法决议抛出imp = forward_imp进入消息转发流程。转发流程分两步快速转发慢速转发

快速转发

forwardingTargetForSelector

打开xcode command + shift +0,然后全局搜索forwardingTargetForSelector

截屏2021-08-27 上午11.26.29.png

  • forwardingTargetForSelector含义是返回未识别消息重定向的对象,简单理解指定一个对象,让这个对象去接收这个消息

实例探究

定义NBPerson类不实现sayHello

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

@implementation NBPerson
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [[NBTeacher alloc] init];
    }
    return  [super forwardingTargetForSelector:aSelector];
}
@end


定义NBTeacher类事项sayHello

@interface NBTeacher : NSObject

@end

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

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        NBPerson *nb = [NBPerson alloc];
        [nb sayHello];

    }
    return 0;
}
  • 打印结果显示:NBTeacher类和NBPerson类没有任何关系,但是指定给NBTeacher类对象,仍然最后可以查询到,并且没有崩溃消息,其实消息在查询过程中先去继承链中去查找,最后没找到。于是系统把这个权限丢给开发者,让开发者直接指定对象和类能接收这个消息。

慢速转发

  • methodSignatureForSelector的含义是返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。
  • methodSignatureForSelector一般搭配和forwardInvocation使用,如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation
@implementation NBPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return  [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
       NSLog(@"---%@---",anInvocation);
}

@end

//此时methodSignatureForSelector的返回值是nil,慢速转发完成,直接闪退

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

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@end

//输出结果
2021-08-27 19:33:53.645285+0800 KCObjcBuild[64750:1211770] ---<NBPerson: 0x100b1e3d0>---sayHello
  • 如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation进行实物处理anInvocation保存了NSMethodSignature签名信息,还有目标方法的方法签名sel,以及方法的接收者。此时不会报奔溃信息,当然也可以处理anInvocaion事务
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NBTeacher * teacher = [NBTeacher alloc];
    anInvocation.target = teacher;
    anInvocation.selector = @selector(sayHello);
    [anInvocation invoke];
}

//打印结果
2021-08-27 19:43:16.418571+0800 KCObjcBuild[65158:1217835] ----[NBTeacher sayHello]---
  • anInvocation.target是teacher对象anInvocation.selector是teacher的sayHello方法
  • [anInvocation invoke]触发消息的调用
结论
  • 快速转发:通过forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程
  • 慢速转发:通过methodSignatureForSelectorforwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束,如果有返回值forwardInvocation的事务处理不处理都不会崩溃