Runtime analysis of the underlying principles objc_megSend

I. Introduction

Recently, I want to look at Runtime underlying principles, then download a runtime source code, the learning process is also access to a lot of information, ask a lot of big cattle. Now summarize my harvest.
Runtime is a set of C , C ++ , Assembler written for the OC offers something runtime mechanism. Runtime source code may be in Apple's official website opensource download to, then I downloaded the latest version of objc4-750 , click this address can go to download .

1802326-cc03a85cef30aabf.png
objc4-750.png

Two, IMP and objc_msgSend

1, SEL and IMP

First we learn what SEL and IMP .

1802326-8cf44822b6411f49.png
IMP.png

Download from the runtime source code, we can see IMP definition:
"IMP is a pointer to a function pointer concrete realization" .
The first two parameters of the function body is id (message recipient, i.e. the object), and the SEL (name of the method).
Analogy: Contents of the book ( SEL ) - Page ( IMP : a pointer to a function of the specific implementation) - specific content (function implementation)
then SEL is how to find the IMP is it?

For example: There is a Person class, initialize a xiaoming object.

1802326-d4995597a723936c.png
Person.png

Then in the study hit a breakpoint before execution method, and run, run to breakpoint when we open the compilation debug mode, follow these steps Debug-> Debug Workflow -> Always Show the Disassembly .

1802326-f28979e74466c68b.png
debug compilation mode .png

我们就来到了一个汇编的界面,如下图,观察可看到在allocinitstudy后面都有 objc_msgSend 函数的调用。
注意:在模拟器上运行才能看到右侧那些alloc、init、study函数名的打印,真机上是看不到的)

1802326-19478ef7bb087d92.png
objc_msgSend函数.png

每个方法都调用了objc_msgSend 函数,很有意思不是吗?但当我们在工程里想搜索objc_msgSend看一下里面什么样时却发现没有,这时我们下载的 runtime 源码就派上用场了。

2、objc_msgSend

说到 Runtime ,就不能不提 objc_msgSend 消息转发。
Objective-C 中,消息是直到运行的时候才和方法实现绑定的。编译器会把一个消息表达式,

[receiver message]

转换成一个对消息函数 objc_msgSend 的调用。
该函数有两个主要参数:消息接收者 receiver 和消息对应的方法名字 selector ——也就是方法选标:

objc_msgSend(receiver, selector)

可同时接收消息中的任意数目的参数:

objc_msgSend(receiver, selector, arg1, arg2, ...)

该消息函数做了动态绑定所需要的一切:

  • 它首先找到选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。
  • 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传给找到的方法实现。
  • 最后,将方法实现的返回值作为该函数的返回值返回

三、objc_msgSend 源码分析流程

接下来我们在 Runtime 中跟踪一下 objc_msgSend 的整个流程。
objc_msgSend 查找分为两种方式:

  • 1、快速查找:在缓存找,属于汇编部分,cache_timp、哈希表。
  • 2、慢速查找:属于 C/C++ 部分。

从源码中我们可以找到 objc_class,可以得知,每个类都有一个缓存 cache,存放着方法的 selimpselimp 最终会组成一张哈希表,这样通过 sel 可以快速的查找到 imp,所以当我们查找一个方法的时候,首先查找的就是这个 cache

1802326-811b8783a29573ef.png
cache.png

1、快速查找部分(汇编部分)

1802326-1c51519019db888a.png
汇编部分快速查找.png

这部分属于汇编部分,涉及了汇编语言的语法,虽然我不熟悉汇编语言,但是还是能够找到一些关键的地方,理解整个流程的走向。

首先,在下载好的 runtime 源码中搜索 _objc_msgSend,选择查看 arm64 下的,也就是 objc-msg_arm64.s 如图:

1802326-1581397e0039822c.png
_objc_msgSend.png

通过sel找到imp:
然后在 objc-msg_arm64.s 中搜索查看 ENTRY _objc_msgSend
流程首先执行的是 ENTRY _objc_msgSend

1802326-59a11d74aafa5260.png
_objc_msgSend流程.png

然后再进行 LNilOrTagged 判断,
判断是否为 nil 或者是否支持 tagged_pointers 类型,
nil 或者不支持就走 LRetrunZero,执行 END_ENTRY _objc_msgSend 结束。

1802326-3e5901f767eee7ac.png
LReturnZero.png

如果不为 nil 并且支持,就走 LGetIsaDone 执行完毕

1802326-20eda13e845d3137.png
LGetIsaDone.png

Next in the LGetIsaDoneexecution in CacheLookup NORMAL(looking from cache imp)
If there is a cache, directly calls imp, otherwise objc_msgSend_uncached.
Let us look CacheLookup, it is a macro, if found in the cache, the execution CacheHitdid not find the execution CheckMiss, but did not find elsewhere have found, can be addadded to the cache go.

1802326-09f8740e685239e1.png
CacheLookup.png
CacheHit And CheckMiss were also all macros, which has the appropriate action, will not go again go into detail. As long as we know that, if not found, CheckMiss the execution of a __objc_msgSend_uncached method.
1802326-9bf362fe87db0423.png
__objc_msgSend_uncached detailed .png

MethodTableLookupIs a macro, this is a list of methods, is also a core focus.

1802326-98a91f99053c58b3.png
MethodTableLookup.png

So far we have found that the search can not __class_lookupMethodAndLoadCache3, at this time may lose confidence, but do not worry, we are able to search _class_lookupMethodAndLoadCache3,
_class_lookupMethodAndLoadCache3it is a C ++ function, so from the beginning to compile C ++ in the.

Next, look objc-runtime-new.mm in_class_lookupMethodAndLoadCache3

1802326-f2128b2c46bf836b.png
_class_lookupMethodAndLoadCache3.png

Quick Search (compilation stage) is now complete!
Because quickly find did not find, so slow to find part of (C / C ++ part) begins.

2, slow searching section (C / C ++ section)

1802326-cca81468c63aa14a.png
Slow part .png Find

现在首先看 当前类 的缓存里现在有没有了,如果有,则返回 imp 。如果没有,则执行getMethodNoSuper_nolock,在当前类查找,如果找到了,则执行 log_and_fill_cache,把 imp 存到缓存中去,并返回要查找的 imp。这样下次再找的时候,就会直接进行汇编快速查找,直接CacheHit了。

1802326-5feb4973a6f7cef8.png
tryThisClassCache.png

如果当前类也没有,则查找当前类的父类,对父类进行 for 循环,因为最终的父类都是 NSObjectNSObject 的父类则是 nil 了,所以我们只遍历到 NSObject。如果父类里有缓存,那么通用把 imp 存到缓存中去,并返回要查找的 imp。如果没有缓存,就执行getMethodNoSuper_nolock

1802326-469567e28bbc9759.jpg
findsuper.jpg

如果父类中也没有,那么就开始下一个步骤,动态方法解析

3、动态方法解析 和 消息转发

1)消息转发流程简述

当一个方法没有找到的时候,会经历几个步骤才会崩溃,先是经过动态方法解析步骤,如果消息还未得到处理,则进入forwardingTargetForSelector:,还未处理则进入methodSignatureForSelector:forwardInvocation
下面是一个消息转发流程的简图。

1802326-2957af10e2361501.png
消息转发流程.png

在这几个步骤我们都可以设法拦截崩溃信息,处理未处理的消息。我们可以对 crash 进行自定义处理,防止崩溃的发生。也可以把 crash 收集起来发给服务器。

2)动态方法解析

下面详细看一下动态方法解析的具体流程,首先如果父类中没有找到 imp,那么开始进行动态方法解析,执行_class_resolveMethod。由于传进来的参数 resolverYEStriedResolver 默认第一次是NO,可以进入判断,但是只会调用一次,调用过后就会把 triedResolver 设为 YES

1802326-fc75f68564fa26c6.png
动态方法解析.png

_class_resolveMethod方法中,判断如果是 元类,则执行_class_resolveInstanceMethod,否则执行_class_resolveClassMethod之后再执行_class_resolveInstanceMethod

问:
为什么执行完_class_resolveClassMethod之后会再次执行一次_class_resolveInstanceMethod
答:
比如有一个类 Person,我们查找 Person 的一个类方法,如果没找到,会继续找他的第一个 元类,再找不到,会继续找 根元类 ,最终会找到 NSObject
Person(类方法) 找——> 元类(实例方法) 找——> 根元类(实例方法) 找——> NSObject(实例方法)
实例方法存在类对象里面,类方法存在元类里面。

1802326-914c3acc127bb0e0.png
屏幕快照 2019-05-22 下午6.38.39.png

所以最终,还会执行一次 _class_resolveInstanceMethod

1802326-0fc86901c56967aa.png
_class_resolveMethod.png

_class_resolveInstanceMethod的内部实现,实质就是 消息的发送

1802326-ec2fcfda29f3fbae.png
_class_resolveInstanceMethod.png

For example we call the Person object methods like run , but Person.m not achieved run method, and the parent does not, then we will open the following dynamic method resolution,
rewriting resolveInstanceMethoddynamically target analysis methods,
rewritten resolveClassMethoddynamically parsing class method.

// .m没有实现,并且父类也没有,那么我们就开启下面的动态方法解析
#pragma mark - 动态方法解析

#import "LGPerson.h"
#include <objc/runtime.h>

@implementation Person

//- (void)run{
//    NSLog(@"%s",__func__);
//}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(run)) {
        // 我们动态解析我们的 对象方法
        NSLog(@"对象方法解析走这里");
        SEL readSEL = @selector(readBook);
        Method readM= class_getInstanceMethod(self, readSEL);
        IMP readImp = method_getImplementation(readM);
        const char *type = method_getTypeEncoding(readM);
        return class_addMethod(self, sel, readImp, type);
    }
    return [super resolveInstanceMethod:sel];
}


+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(walk)) {
        // 我们动态解析我们的 类方法
        NSLog(@"类方法解析走这里");
        SEL hellowordSEL = @selector(helloWord);
        // 类方法就存在我们的元类的方法列表
        // 类 类犯法
        // 元类 对象实例方法
        Method hellowordM1= class_getClassMethod(self, hellowordSEL);
        Method hellowordM= class_getInstanceMethod(object_getClass(self), hellowordSEL);
        IMP hellowordImp = method_getImplementation(hellowordM);
        const char *type = method_getTypeEncoding(hellowordM);
        NSLog(@"%s",type);
        return class_addMethod(object_getClass(self), sel, hellowordImp, type);
    }
    return [super resolveClassMethod:sel];
}

If the dynamic analysis step is also no solution is found, then proceeds to the next step. Message forwarding .

3) message forwarding

<1>, forwardingTargetForSelectormethods:
for example, in addition to our Person class, there is a Dog class, this class have run method, then when we judge to be run when the method can not be found, you can forward the message to the Dog class.

#pragma mark - 消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(run)) {
        // 转发给我们的 Dog 对象
        return [Dog new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

<2> methodSignatureForSelectorMethod:
If forwardingTargetForSelector no interceptions live, you can only use the last hurdle methodSignatureForSelector, the acquisition method signature, and then handed over to forward the message forwardInvocation.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(run)) {
        // 获取方法签名
        Method method    = class_getInstanceMethod(object_getClass(self), @selector(readBook));
        const char *type = method_getTypeEncoding(method);
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];// 移交给消息转发
    }
    return [super methodSignatureForSelector:aSelector];
}

// 消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    NSLog(@"------%@-----",anInvocation);
    anInvocation.selector = @selector(readBook);
    [anInvocation invoke];
}

If all of the above steps do not successfully processed the message, it will collapse. For example, I did not find the study method

1802326-a3d02b72a5f9710e.png
unrecognized.png
The most common unrecognized selector error message is actually made this objc_defaultForwardHandler print function
1802326-9a5fd9aceadceb0a.png
objc_defaultForwardHandler.png

These are my Runtime messages forwarded objc_megSenda major underlying principles summarized. If there is wrong place, please also help point out, thank you, mutual progress.

Summing up the above reference to the following article and partially excerpt, the author is very grateful to share! :
1, "System Programming Guide Objective-C 2.0 runtime"
2, Huang Wenchen author of "iOS Runtime explain the SEL, Class, id, IMP, _cmd, isa, method, Ivar"

Reprinted please note the original source, not for commercial propagation - where, how many

Reproduced in: https: //www.jianshu.com/p/0d6dfdb20318

Guess you like

Origin blog.csdn.net/weixin_33704591/article/details/91063383