IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS开发底层之消息的快速与慢速转发 - 11 -> 正文阅读

[移动开发]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. 慢速转发方法 methodSignatureForSelectorforwardInvocation
  3. doesNotRecognizeSelector 系统发现你没有做任何补救处理,就会调用这个方法。

三、resolveInstanceMethod 为啥调用了2次。

  1. 第一次跑进来是imp查找在快速查找与慢速查找后,还没有找到,走进入动态协议流程, 进入 resolveMethod_locked方法 ,调用第一次。
  2. 第二次跑到这个方法,首先要看下崩溃的时候堆栈信息,看看系统做了什么 ,通过bt看看堆栈信息
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff204d792e libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x0000000100461e79 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff2045b411 libsystem_c.dylib`abort + 120
    frame #3: 0x00007fff204c9ef2 libc++abi.dylib`abort_message + 241
    frame #4: 0x00007fff204bb5fd libc++abi.dylib`demangling_terminate_handler() + 266
  * frame #5: 0x00000001002f7fd2 libobjc.A.dylib`_objc_terminate() at objc-exception.mm:701:13 [opt]
    frame #6: 0x00007fff204c9307 libc++abi.dylib`std::__terminate(void (*)()) + 8
    frame #7: 0x00007fff204cbbeb libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
    frame #8: 0x00007fff204cbbb2 libc++abi.dylib`__cxa_throw + 116
    frame #9: 0x00000001002f7acf libobjc.A.dylib`objc_exception_throw(obj=<unavailable>) at objc-exception.mm:591:5 [opt]
    frame #10: 0x00007fff206fc38d CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    frame #11: 0x00007fff205e190b CoreFoundation`___forwarding___ + 1448
    frame #12: 0x00007fff205e12d8 CoreFoundation`_CF_forwarding_prep_0 + 120
    frame #13: 0x0000000100003a9a KCObjcBuild`main(argc=<unavailable>, argv=<unavailable>) at main.m:26:9 [opt]
    frame #14: 0x00007fff20521f5d libdyld.dylib`start + 1

这个地方发现了CoreFoundation调用了 ___forwarding___这个方法。 结合上面___forwarding___的反汇编,得出在慢速转发流程中调用了CoreFoundation框架中的methodSignatureForSelector后,会再次进入动态决议,所以这个地方调用了两次 resolveMethod_locked


总结

1.快捷键

lldb命令: bt  //打印堆栈信息。 

command + shift + 0 : 打开苹果官方开发文档。

2. IMP查找流程总结

  1. 快速查找流程 - 在类的缓存cache中查找指定方法的实现

  2. 慢速查找流程 - 在类的方法列表中查找,如果还是没找到,则去父类的缓存和方法列表中查找,没有找到,提柜父类的父类直到 nil.

  3. 动态方法决议 - 第一次补救程序崩溃的机会,解决方法为增加resolveInstanceMethod(实例方法调用) resolveClassMethod (类方法调用)

  4. 消息转发 - 第二次补救崩溃机会 ,快速转发, 增加 forwardingTargetForSelector

  5. 消息转发 慢速转发 - 第三次补救崩溃机会 增加方法为 一对, 分别是 methodSignatureForSelectorforwardInvocation

如果三次机会都没有把握住,则程序直接报错崩溃。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-09 17:36:08  更:2021-07-09 17:36:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 8:43:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码