Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)
  5. The underlying principles of the ISA class (middle)
  6. The underlying principles of isA-Class (part 2)
  7. Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
  8. Objc_msgSend explores the underlying principles of iOS
  9. Runtime Runtime slow lookup process
  10. Dynamic method resolution for exploring the underlying principles of iOS

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

preface

Following the dynamic method resolution process of the last article, we came to the inquiry of the message forwarding process, everyone cheer up, refueling together!

To prepare

NSMethodSignature

To help the runtime system, the compiler encodes the return type and parameter type of each method into a string and associates that string with the method selector. It uses an encoding scheme that is useful in other environments as well, and can therefore be used publicly through the @encode() compiler directive. When given a type specification, @encode() returns the string encoding for that type. A type can be a primitive type, such as an int, a pointer, a marked structure or union, or a class name — in fact, any type that can be used as an argument to the C sizeof() operator.

Type Encodings — from Apple

We define a SMPerson class:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc SMPerson.m -o SMPerson.cpp

You can then find the following in the smPerson.cpp file:

resolveInstanceMethod

On an article in the end, after the resolution resolveInstanceMethod dynamic method is according to log into the forwardingTargetForSelector method, apple document is as follows:

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.

Copy the code

The simple explanation is the object to which an unrecognized message should be returned first. That’s just a redirect.

inSMPersonYou can see that before the program crashes, it executesresolveInstanceMethodMethod.

Next, let’s implement the following method in SMTeacher:

The program ran flawlessly. The fast forward process here is not to the method of dynamic resolution there before, code to add long, bloated, here, we only need to return an we can handle object can (for example, we have a specialized processing does not implement methods of class object, the object can add to it every time you fast forwarding a method).

So, if we redirect this past class, what if it’s not implemented? Then we can only message through methodSignatureForSelector slowly forward.

methodSignatureForSelector

The Apple documentation is as follows:

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 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.Copy the code

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

Let’s implement it. After running, it still crashes:

(void)for (NSInvocation *)anInvocation; Methods used together can, and need to return a NSMethodSignature can in methodSignatureForSelector.

At our system level, all method functions are called messages of the system, also known as transactions. Transactions may or may not be executed. When the message forwarding process is in the slow process, we return a method signature to the system, and the system will not execute the transaction by default, but the system will retain the transaction. That is the above – (void)for (NSInvocation *)anInvocation. We can print it out here, and that’s why Apple asked us to do it with this method, to have a place to let us know which methods came into the slow lookup process and didn’t get rid of it.

After the method is executed here, it is not difficult to see that it is actually a mistake. Since it is a mistake, there is no need to execute it again. The error is silently disappeared through the system invocation. Finally, here, the developer can do the corresponding processing, or not do any processing, the system will not report an error. For example, let’s deal with it like this:

If we extract this method from the NSObject classification, the system will be a little bit more stable and won’t crash because we don’t have an implementation method; However, this is not recommended.

To explore the way of thinking

Back to the beginning, our method didn’t work, and after we ran it, the system crashed. At this point, don’t panic. A programmer grows when the system crashes. We can use LLDB to dynamically debug bt to check the current stack information:

DoesNotRecognizeSelector method is a method that is called after the message is not processed by either the fast lookup process or the slow lookup process. It falls under the CoreFoundation framework, which may be a clue.

CoreFoundation source

Then I went to apple’s CoreFoundation source code, and I started,

When I opened it, I didn’t find any content. So, the apple is still the apple. It doesn’t have all the contentsCoreFundationOpen source to you, so this step is not going to work. A quick search of Apple’s documentation didn’t turn up any clues. So, we need to find a dynamic library to disassemble the analysis.

Disassembly explores CoreFoundation

coreFoundation

Open the CoreFundation we found with Hooper, then search for Forwarding and find that ___forwarding___ was indeed called after _CF_forwarding_prep_0.

Click on ___forwarding___

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;
}
Copy the code

At this point, the exploration of the fast forwarding process and the slow forwarding process of the message is completed.

Dynamic message forwarding process