iOS开发底层之消息的快速与慢速转发 - 11

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之消息的快速与慢速转发 - 11相关的知识,希望对你有一定的参考价值。

文章目录


一、instrumentObjcMessageSends 系统日志探索

1. 它是什么?

instrumentObjcMessageSends是系统的日志,是苹果的私有API,我们可以控制log开关,打印日志信息。

2. 它的由来?

//在查看 IMP 
//1.lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) 方法的时候。 在查找到IMP, done 后续,
//2. 调用了 log_and_fill_cache(cls, imp, sel, inst, curClass);
// 源码如下
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);

// 3. 发现了 objcMsgLogEnabled 这个字段,,全局搜索下这个字段。 发现它默认是false,就只有在一个地方进行了设置

//4. 设置的方法为 void instrumentObjcMessageSends(BOOL flag) ,所以我们可以在源码中,先申明这个方法,然后在调用,系统会帮我们找到他的实现。 

3. 日志位置

快捷键: command + shift + g   
文件位置为:  /tmp/msgSend-***

4. 代码使用

//1. 定义下,不然会报找不到这个api错误
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) 
    @autoreleasepool 
         // 2. 打开记录日志操作
        instrumentObjcMessageSends(true);
        Person *person = [Person alloc];
        [person sayHello];
        // 3. 关闭日志操作
        instrumentObjcMessageSends(false);
        NSLog(@"Hello, World!");
    
    return 0;


二、消息转发流程

imp查询流程总结

为了查找到 imp, 总结下一共经过的流程,防止思路跟不上。
0. 寻找 sel对应的 imp  
1. objc_msgSend  cache  快速查找
2. 慢速方法查找   methlist 
3. 动态方法决议   resolveInstanceMethod
4. 消息快速转发(让别人做 ,一般可以定义一个专门的对象,进行消息处理)   forwardingTargetForSelector 
5. 消息慢速转发 , (比快速转发更加灵活)  methodSignatureForSelector 和 forwardInvocation 配对出现。 

1.动态方法决议后续转发流程

上面通过系统的日志,故意让方法找不到,让系统崩溃,然后打开日志看到整个的方法转发过程,里面有好几个方法。

+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject methodSignatureForSelector:
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject doesNotRecognizeSelector:
- LGPerson NSObject doesNotRecognizeSelector:

第一个方法resolveInstanceMethod这个是我们上篇博客介绍的,方法的动态决议。
重点看后面的方法。

2. forwardingTargetForSelector 快速转发流程

// 对象方法快速转发
-(id)forwardingTargetForSelector:(SEL)aSelector 
    NSLog(@"%@ : %s",self,__func__);
//     背锅侠 - 专业处理没有响应的方法
// 方式二: 指定对象,去添加方法并实现。
//    IMP myImp =  class_getMethodImplementation([LGTeacher class], aSelector);
//    Method method = class_getInstanceMethod([LGTeacher class], aSelector);
//
//    const char *type = method_getTypeEncoding(method);
//
//    bool isSuccess = class_addMethod([LGTeacher class], aSelector, myImp, type);
    // 方式一: 知道这个对象里面实现了方法
    return [LGTeacher alloc];

3. methodSignatureForSelector慢速转发流程

// 对象方法的慢速转发  相比于快速转发更加灵活,可以选择处理或者不处理。 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 
    NSLog(@"%@ : %s",self,__func__);
    if (aSelector == @selector(say666))  // 拦截这个方法。 这里没有实现,只是保存这个事务,
        
        Method method = class_getInstanceMethod([LGTeacher class], aSelector);
        const char *type = method_getTypeEncoding(method);
        return [NSMethodSignature signatureWithObjCTypes:type];
    
    
    return  [super methodSignatureForSelector:aSelector];
    


// 和methodSignatureForSelector 成对出现。不然拦截不到错误。
- (void)forwardInvocation:(NSInvocation *)anInvocation 
    
    SEL selector = anInvocation.selector ;
    LGTeacher *teacher = [[LGTeacher alloc] init];
    
    if ( [self respondsToSelector:selector])  // 自己能处理
        [anInvocation invoke];
     else if ([teacher respondsToSelector:selector])  // 指定对象处理
        [anInvocation invokeWithTarget:teacher];
        
     else  // 不想处理 , 可上传日志给服务器,保存记录。
        
        NSLog(@"----%@------%@--", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    
    

4.hoper反编译cf

  1. lldb命令: bt //打印堆栈信息。 查看下崩溃的堆栈里面有什么信息。

    上面的图片,查到崩溃信息,发现最终定位到 CoreFoundation 中的 forwarding,那他最后到底做了什么呢, 只能通过查看CoreFoundation 反编译下,才能揭晓它到底做了什么?

  2. 准备: 需要下载 hopper 软件。 下载hopper

    CoreFoundation 动态库。

把 CoreFoundation拖入Hopper 查看下 ,然后搜索

___forwarding___

看到伪代码为:

int ____forwarding___(int arg0, int arg1) 
    rsi = arg1;
    rdi = arg0;
    r15 = rdi;
    rcx = COND_BYTE_SET(NE);
    if (rsi != 0x0) 
            r12 = *_objc_msgSend_stret;
    
    else 
            r12 = *_objc_msgSend;
    
    rax = rcx;
    rbx = *(r15 + rax * 0x8);
    rcx = *(r15 + rax * 0x8 + 0x8);
    var_140 = rcx;
    r13 = rax * 0x8;
    if ((rbx & 0x1) == 0x0) goto loc_649bb;

loc_6498b:
    rcx = **_objc_debug_taggedpointer_obfuscator;
    rcx = rcx ^ rbx;
    rax = rcx >> 0x1 & 0x7;
    if (rax == 0x7) 
            rcx = rcx >> 0x4;
            rax = (rcx & 0xff) + 0x8;
    
    if (rax == 0x0) goto loc_64d48;

loc_649bb:
    var_148 = r13;
    var_138 = r12;
    var_158 = rsi;
    rax = object_getClass(rbx);
    r12 = rax;
    r13 = class_getName(rax);
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
    rax = [rdi forwardingTargetForSelector:var_140];
    if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

loc_64a19:
    r12 = var_138;
    r13 = var_148;
    if ((rax & 0x1) == 0x0) goto loc_64a5b;

loc_64a2b:
    rdx = **_objc_debug_taggedpointer_obfuscator;
    rdx = rdx ^ rax;
    rcx = rdx >> 0x1 & 0x7;
    if (rcx == 0x7) 
            rcx = (rdx >> 0x4 & 0xff) + 0x8;
    
    if (rcx == 0x0) goto loc_64d45;

loc_64a5b:
    *(r15 + r13) = rax;
    r15 = 0x0;
    goto loc_64d82;

loc_64d82:
    if (**___stack_chk_guard == **___stack_chk_guard) 
            rax = r15;
    
    else 
            rax = __stack_chk_fail();
    
    return rax;

loc_64d45:
    rbx = rax;
    goto loc_64d48;

loc_64d48:
    if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;

loc_64d55:
    *(r15 + r13) = _getAtomTarget(rbx);
    ___invoking___(r12, r15);
    if (*r15 == rax) 
            *r15 = rbx;
    
    goto loc_64d82;

loc_64ed1:
    ____forwarding___.cold.4();
    rax = *(rdi + 0x8);
    return rax;

loc_64a67:
    var_138 = rbx;
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:
    rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
    r14 = var_138;
    var_148 = r15;
    if (rax == 0x0) goto loc_64dd7;

loc_64ab2:
    rax = [r14 methodSignatureForSelector:var_140];
    rbx = var_158;
    if (rax == 0x0) goto loc_64e3c;

loc_64ad5:
    r12 = rax;
    rax = [rax _frameDescriptor];
    r13 = rax;
    if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) 
            rax = sel_getName(var_140);
            rcx = "";
            if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) 
                    rcx = " not";
            
            r8 = "";
            if (rbx == 0x0) 
                    r8 = " not";
            
            _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, stack[-360]);
    
    rax = object_getClass(r14);
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_150 = r13;
    if (rax == 0x0) goto loc_64c19;

loc_64b6c:
    if (*0x5c2700 != 0xffffffffffffffff) 
            dispatch_once(0x5c2700, ^ /* block implemented at ______forwarding____block_invoke */  );
    
    r15 = [NSInvocation requiredStackSizeForSignature:r12];
    rsi = *0x5c26f8;
    rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
    r13 = &stack[-360];
    __bzero(r13, rsi);
    ___chkstk_darwin(r13, rsi, r12, rcx);
    rax = objc_constructInstance(*0x5c26f0, r13);
    var_140 = r15;
    [r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
    [var_138 _forwardStackInvocation:r13];
    r14 = 0x1;
    goto loc_64c76;

loc_64c76:
    if (*(int8_t *)(r13 + 0x34) != 0x0) 
            rax = *var_150;
            if (*(int8_t *)(rax + 0x22) < 0x0) 
                    rcx = *(int32_t *)(rax + 0x1c);
                    rdx = *(int8_t *)(rax + 0x20) & 0xff;
                    memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
            
    
    rax = [r12 methodReturnType];
    rbx = rax;
    rax = *(int8_t *)rax;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) 
            r15 = *(r13 + 0x10);
            if (r14 != 0x0) 
                    r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
                    [r13 release];
                    rax = *(int8_t *)rbx;
            
            if (rax == 0x44) 
                    asm  fld        tword [r15] ;
            
    
    else 
            r15 = ____forwarding___.placeholder;
            if (r14 != 0x0) 
                    r15 = ____forwarding___.placeholder;
                    [r13 release];
            
    
    goto loc_64d82;

loc_64c19:
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    [r14 forwardInvocation:rax];
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

loc_64ec2:
    rdi = &var_130;
    ____forwarding___.cold.3(rdi, r14);
    goto loc_64ed1;

loc_64e3c:
    rax = sel_getName(var_140);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != var_140) 
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[-360]);
    
    if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) 
            ____forwarding___.cold.2(var_138);
    
    (*_objc_msgSend)(var_138, @selector(doesNotRecognizeSelector:));
    asm  ud2 ;
    rax = loc_64ec2(rdi, rsi);
    return rax;

loc_64dd7:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) 
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[-360]);
    
    else 
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[-360]);
    
    goto loc_64e3c;

loc_64dc1:
    r14 = @selector(forwardingTargetForSelector:);
    ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
    goto loc_64dd7;

说明:
是不是太惊喜了。 CoreFoundation底层是按照这个顺序依次进行调用的。

  1. 源码中依次为快速转发方法 forwardingTargetForSelector:(SEL)aSelector
  2. 慢速转发方法 methodSignatureForSelectorfor

    以上是关于iOS开发底层之消息的快速与慢速转发 - 11的主要内容,如果未能解决你的问题,请参考以下文章

    iOS开发底层之方法的慢速查找流程探索+方法动态决议上 - 10

    iOS开发底层之方法的慢速查找流程探索+方法动态决议上 - 10

    iOS底层探索之Runtime: lookUpImpOrForward慢速查找分析

    iOS底层开发消息发送与转发流程

    iOS底层探索之Runtime: 消息转发

    iOS底层探索之Runtime: 动态方法解析