iOS 底层原理探索 之 消息转发流程

1,347 阅读11分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议

以上内容的总结专栏


细枝末节整理


前言

接着上一篇的 动态方法决议流程 ,本篇 我们来到了消息转发流程的探究,大家打起精神,一起加油!

准备

NSMethodSignature

方法的返回值和参数的类型信息的记录, 为了帮助运行时系统,编译器将每个方法的返回类型和参数类型编码为一个字符串,并将该字符串与方法选择器关联起来。它使用的编码方案在其他环境中也很有用,因此可以通过@encode()编译器指令公开使用。当给定类型规范时,@encode()返回该类型的字符串编码。类型可以是基本类型,如int、指针、带标记的结构体或联合,或类名——实际上,可以用作C sizeof()操作符的实参的任何类型。

Type Encodings -- 来自苹果的介绍

我们定义了一个SMPerson类: image.png xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc SMPerson.m -o SMPerson.cpp

之后可以在 SMPerson.cpp文件中找到以下内容:

image.png

Type Encodings.001.jpeg

接着上一篇的提问

为什么所有的方法都会打印两边呢 ?

第一次来自动态方法解析; 第二次来自慢速转发流程。

第一次:

image.png

第二次:

image.png

这一次的调用来自 methodDescriptionForSelecter 签名之后 。 在 invocation 之前 给了一次机会。

也就是在 methodDescriptionForSelecter流程之后 也有一次动态方法解析。无论这次动态方法解析有没有,都必须会来到 forwardInvocation中去。

resolveInstanceMethod 之后的流程又是怎么样的呢?

resolveInstanceMethod

上一篇的最后,在 resolveInstanceMethod 动态方法决议之后,日志内容显示是进入到了 forwardingTargetForSelector 方法,苹果文档如下:

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

Declaration
- (id)forwardingTargetForSelector:(SEL)aSelector;

Parameters
aSelector
A Selector for a method that the receiver does not implement.

Return Value
The object to which unrecognized messages should first be directed.

Discussion
If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.

简单解释一下就是 返回无法识别的消息应首先指向的对象。 也就是重定向一下。

SMPerson 中做一下实现,可以看到在程序崩溃之前,执行了 resolveInstanceMethod 方法中的内容。 image.png

接下来我们在 SMTeacher 中实现一下 方法:

image.png 程序完美的跑完了。此处的快速消息转发流程并不向之前的动态方法决议那里,代码添加的又长又臃肿, 在这里,我们只需要返回一个我们可以处理对象就可以(比如说,我们有一个专门处理没有实现方法的类对象,这个对象每次快速转发时都可以向它添加一个方法)。

那么,如果说我们重定向过去的这个类,万一没有实现呢?接下来就我们就只能通过 methodSignatureForSelector 来进行消息的慢速转发。

methodSignatureForSelector

苹果文档介绍如下:

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

Declaration
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

Parameters
aSelector
A Selector that identifies the method for which to return the implementation address. When the receiver is an instance, aSelector should identify an instance method; when the receiver is a class, it should identify a class method.

Return Value
An NSMethodSignature object that contains a description of the method identified by aSelector, or nil if the method can’t be found.

Discussion
This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.

See Also
Related Documentation
- forwardInvocation:
Overridden by subclasses to forward messages to other objects.

简答解释一下就是 : 返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。

接下来我们实现一下,运行后,还是奔溃了:

image.png

再详细看看苹果的文档,发现,需要搭配 - (void)forwardInvocation:(NSInvocation *)anInvocation; 方法一起使用才可以,并且需要在 methodSignatureForSelector 中返回一个NSMethodSignature 才可以。

image.png

在我们的系统层面,所有的方法函数都称为系统的消息,也叫做事务。 事务可以去执行,也可以不去执行。在消息转发流程中走到了慢速流程中,我们返回给了系统一个方法签名,系统会默认不执行此次事务,但是系统也会保留此次的事务。也就是上面的 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法的参数 anInvocation。这里我们可以打印一下,这也就是苹果让我们搭配这个方法实现的原因吧,有地方可以让我们知道,哪些方法来到了慢速查找流程,并且没有处理掉。

image.png

方法执行到这里,不难看出其实就是一个错误,既然是一个错误,又何必再去执行下去,通过系统的forwardInvocation将这个错误默默的消失掉了。最后在这里,开发人员可以做对应的处理,也可以不做任何处理,系统也不会报错。 比如我们这样去处理:

image.png 如果说我们在NSObject分类中提炼出来此方法,系统会稳定一些并不会因为没有实现方法而崩溃;然而,并不推荐这么去处理。

探索思路

回到刚开始,我们的方法没实现,运行后,系统奔溃了。这个时候,不要慌,一个程序员的成长都是伴随着系统奔溃的时候。此时我们可以通过 lldb 动态调试指令 bt, 查看一下当前的堆栈信息:

image.png

堆栈信息显示了程序奔溃时刻的调用函数的信息, 其中 doesNotRecognizeSelector方法是在 消息的快速查找流程和慢速查找流程都没有处理之后,系统调用的方法。其属于CoreFoundation 框架下,可能是一个线索哦。

CoreFoundation源码

然后来到苹果的 CoreFoundation源码,接着搞起,

image.png

image.png 打开后没有发现任何的内容,所以,苹果还是那个苹果,它并没有将全部的 CoreFundation 开源给你,所以这一步走不通了。 接着,我们在苹果的文档内容中一顿搜索查阅也并没有发现什么线索。 所以,我们需要找一个动态库来进行反汇编分析一下了。

反汇编探索CoreFoundation

coreFoundation

使用Hooper打开我们找到的CoreFundation, 然后搜索 forwarding 发现在 _CF_forwarding_prep_0 之后系统确实 调用了 ___forwarding___

image.png

点击进入 ___forwarding___

image.png

int ____forwarding___(int arg0, int arg1) {
    rsi = arg1;
    rdi = arg0;
    r15 = rdi;
    rcx = COND_BYTE_SET(NE);
//根据参数 arg1 判断是通过 _objc_msgSend_stret 或者 _objc_msgSend发送消息
    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);
	//判断当前 rax 是否响应 方法  forwardingTargetForSelector
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
// 如果可以响应则直接调用 forwardingTargetForSelector 也就是快速响应流程
    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;

//如果当前类实现了  methodSignatureForSelector 方法
loc_64a8a:
    rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
    r14 = var_138;
    var_148 = r15;
    if (rax == 0x0) goto loc_64dd7; //并且 rax (返回值不为空), 即返回了方法签名

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);
// 经过一些参数整理 、容错处理 , 最后调用了 _forwardStackInvocation ,经查证,此方法为系统层面内部调用方法,未对外暴露,开发者无法调用
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_150 = r13;
    if (rax == 0x0) goto loc_64c19;

//******************************************************************************************


//****************************************************************************************** invocation 调用前的准备工作
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]; // 方法签名 例如 v@:@
    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:
// 判断类是否可以响应 forwardInvocation 方法
    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;
}

至此,消息的快速转发流程 和 慢速转发流程就探索完毕了。

消息动态转发机制流程

消息动态转发流程.001.jpeg