iOS | runtime

本篇文章主要从实操角度出发,切入点分析,切勿死记硬背。例如runtime消息打印的msgSend文件内容是什么,为什么会有object_setClass,method_setImplementation两种不同的方法; 什么情况会用到动态方法解析,什么时候用消息发送消息转发.

强烈建议学完以后好好读一遍英文源码:
developer.apple.com/library/arc…

image.png

1. Runtime基础知识
2. OC方法底层调用过程
3. 消息发送
4. 动态方法解析
5. 消息转发
6. 应用
复制代码

1. Runtime基础知识

现行版本: iPhone程序和Mac OS X v10.5及以后的系统中的64位程序
和运行时系统的交互:

image.png

程序执行的大致过程: 代码--> 编译链接--->执行

c语言,程序编译成什么,运行就调用什么

oc语言,动态语言,程序编译成什么,程序运行的过程中可以动态改变(动态改变方法调用,动态添加方法)

image.png

具体代码在 github.com/tanghaitao/…

2. OC方法底层调用过程

借助clang编译器,在终端执行命令 clang -rewrite-objc main.m
然后将main.cpp文件拖入到工程,记得 取消勾选target,否则会参与编译compile sources
具体代码在 github.com/tanghaitao/…

image.png

objc_msgSend(p, sel_registerName("walk"));
复制代码
NSLog(@"%p %p",@selector(walk),sel_registerName("walk"));
//0x100003fa2 0x100003fa2
复制代码

image.png

从上面可以得出:

     oc方法调用,实际上是objc_msgSend函数的调用。
    本质是消息机制: 消息接收者  消息名称
    objc_msgSend的执行过程大致可以分成三部分
     1.消息发送
     2.动态方法解析
     3.消息转发
复制代码

2.1 消息发送

下面我将结合objc源码 github.com/tanghaitao/… 对objc_msgSend消息发送底层做一个详细的分析:

objc底层源码主要由 汇编 c c++组成。

image.png

如上图所示:搜索objc_msgSend 找到objc-msg-arm64.s 汇编文件ENTRY _objc_msgSend 表示入口
x0 消息接收者 消息名称,比较nil check and tagged pointer check,如果没有消息接收者则返回。
接着找x13 =isa(此处的isa 不止是指针,多了一个与操作),找到了类。
类的缓存中找,CacheLookup Normal

如果找到了(即Hit),就从缓存中找,CacheHit,如下图所示:

image.png

缓存中直接调用imp

image.png

如果没有找到(即CheckMiss),就__objc_msgSend_uncached

image.png

搜索 ___objc_msgSend_uncached,找到 STATIC_ENTRY __objc_msgSend_uncached从MethodTableLookup 方法列表里面查找

image.png

接着调用 __class_lookupMethodAndLoadCache3,搜索后发现搜索不到,因为从汇编进入了c,少一根下划线才能搜索得到

image.png

image.png

上述是通过源码对objc_msgSend的底层原理做详细的分析,下面是总结:

image.png

image.png

2.2 动态方法解析

如果从类的缓存和方法列表,以及父类的缓存和方法列表里面都没找到imp,会执行一次动态方法解析,然后 goto retry,再从类的缓存和方法列表,以及父类的缓存和方法列表这样的步骤去查找一次。

image.png

动态方法解析过程如下图:

image.png

如果不是元类,就会调用实例方法_class_resolveInstanceMethod

如果是元类,就会调用类方法 _class_resolveClassMethod

单纯看源码太枯燥,下面结合具体的例子做讲解:
具体代码在github.com/tanghaitao/…

image.png

image.png

关于 实例对象, 类对象、元类对象我做了一个简单的总结,如下:

    // 实例对象、    类对象、       元类对象,
    //    p   、[p class],    object_getClass("类名")== 等价于if(p) p->getIsa()
    //   实例对象isa找到类,类通过isa找到元类,元类通过is找到根元类,
    // 实例对象存的是特殊的实例方法。   [p method0]
    // 类对象存的是所有特殊实例的方法 - 减号 [p1 method0] [p2 method0] [p3 method0]
    // 元类对象存的是所有的类方法。 + 加号[Person method1];
    
    
复制代码

image.png

动态方法解析: 实例方法 c方法和oc方法
具体代码在github.com/tanghaitao/…

image.png

image.png

类方法的动态方法解析:
具体代码在github.com/tanghaitao/…

image.png

2.3 消息转发

如果消息执行imp动态方法解析``都没有找到,就会进入消息转发_objc_msgForward_impcache

image.png

image.png

image.png

image.png

消息转发源码在objc源码中没有暴露,需要通过api打印出具体的消息转发流程,(当然你也可以使用砸壳、hopper等工具逆向得出,个人不推荐):
具体代码在github.com/tanghaitao/…

extern void instrumentObjcMessageSends(BOOL);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        [[TZPerson new] walk];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
复制代码

image.png

打印结果会输出到 /private/tmp/msgSend-XXX的文件中。先找到/private/tmp,然后定位到末尾,再运行xcode工程,即可发现新添加的msgSend-XXX文件

声明一个walk的实例方法-(void)walk[[Person new] walk];运行后没有imp,动态方法解析resolve也没有,进入消息转发流程

image.png

image.png

msgSends-xxx具体内容:

image.png

image.png

image.png

类方法的消息转发 具体代码在github.com/tanghaitao/…

image.png

关于实例方法和类方法的 消息转发的不同点:
实例方法只能转发到实例方法
类方法可以转发到实例方法和类方法
复制代码

3. API 应用

关于runtime的api 主要的代码:
github.com/tanghaitao/…

例子代码在 github.com/tanghaitao/…

建议大家大概的跑一下,比较基础,不要觉得很难,其实就是一些API的调用而已。

3.1 类的应用

image.png

下面分析一下objc_class的源码

image.png
class_rw_t 表示可以读写表示 在objc_registerClassPair类注册前和注册后都可以添加
class_ro_t 表示 只读 read only,表示 只能在 objc_registerClassPair类注册前添加
从下面图可以看出,实例变量ivars是class_ro_t结构体中,只读,所以在类注册后,不能直接添加实例变量了,当然可以通过runtime的方式变相的添加。
image.png

3.2 方法的应用

image.png

tableview的设置,页面统计,MLeakFinder,数组越界保护等等都有应用。

image.png

3.3 属性变量的应用

看案例github.com/tanghaitao/…
此处省略一千字

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享