引子
在讲正题之前,先插入个小话题:在App加载过程中,会加载dyld。那么dyld是什么了?
dyld就是一个动态链接器:加载所有的库和可执行文件
下面简单说下它的加载流程:
第一步:程序执行从_dyld_start开始
第二步:进入dyld:main函数
第三步:配置一些环境 :rebase_dyld 加载共享缓存
第四步: 实例化主程序 DYLD2 / DYLD3(闭包模式),这一步里面:
1、加载动态库(首先是插入的动态库)(主程序和动态库都会添加到allImages里面:loadAllImages)
2、链接主程序、绑定符号(非懒加载、弱符号)等等
最关键的:初始化方法:initializeMainExecutable-> runInitializers -> processInitializers -> recursiveInitialization -> notifySingle
2.1、在notifySingle函数里面,会执行一个回调,此回调是_objc_init初始化时赋值的一个函数Load_images
2.1.1、在Load_images里面执行class_load_methods函数,还有call_class_loads函数,call_class_loads函数是循环调用各个类的load方法
2.1.2、后面再执行doModInitFunction函数,这个函数内部会调用全局C++对象的构造函数 attribute ((constructor))的C函数
第五步:返回主程序的入口函数。开始进入主程序的main函数!
那么这就是dyld的加载流程了。
如有需要dyld源码的童鞋,可以留言哦o( ̄︶ ̄)o
有了这对dyld的初步的了解,就可以进入正题了~ ~ ~ GO GO GO
LLVM优化alloc
在调试objc的源码中,(objc的源码在iOS的底层探究--------alloc 中就有,可以到那里获取)
声明了一个LGPerson类,然后在main.m里面调用。
我们在这里断点,查看alloc的执行
紧接着进入到alloc方法里面,调用的是 _objc_rootAlloc方法,打上断点
再接着进入 _objc_rootAlloc 方法,在_objc_rootAlloc方法里面,调用的是callAlloc方法,但是在1956行,也是调用的callAlloc方法,打上断点
嘿嘿,做完准备工作后,就可以享受挖掘的雷区了(乐趣 ^_^)
现在进行断点跟踪咯,得看仔细咯。
现在断点刚刚开始断在alloc即将调用的时候。正常情况,是直接进入到alloc的底层实现 也就是下面这个代码处,但实际上是这样子的吗?
+ (id)alloc {
return _objc_rootAlloc(self);
}
我们接着让断点往下走一步。断点直接到了objc_alloc方法,而不是断在+(id)alloc方法上面。
当我们让断点往下再走一步,此时,断点在执行到+(id)alloc方法上
对象初始化是,执行alloc,竟然先到objc_alloc方法,再到+(id)alloc,这是为什么了?
既然我们知道alloc对objc_alloc进行了编译的调节,但是在什么时候进行的,我们却不得而知。
文章最开始的时候,讲了下dlyd的加载流程。这里,我们先直接定位到类的加载映射里面(映射当前的镜像文件),找到_read_images方法
在这个方法里面,有处理映射的方法编号的代码块,就是方法里面的isa指针和imp的关系调整处理
我们进入_read_images方法里面。在这个方法里面,有段关于size_t修复代码,这个意味着我们的isa指针和imp的绑定,一定发生在这个size_t的修复之前,那么_read_images的执行,是在isa指针和imp的绑定之后,也就是在llvm编译之后。
这个修复的方法,就在同文件(objc-runtime-new.mm)8443行,从这里面,可以看出,当alloc出问题时,作出修复。把走歪的路线掰回正轨来。当msg->imp == &objc_msgSend_fixup时,如果msg->sel == @selector(alloc), msg->imp 就回对 (IMP)&objc_alloc进行一个标记;当然,如果没有出现问题,就直接执行objc_alloc方法,不用再进行修复。在_read_images方法里面,做了一层fixupMessageRef的修复。
刚刚提到过,_read_images的执行,是在isa指针和imp的绑定之后,也就是在llvm编译之后。
因为苹果对一些方式是做了hook处理的,就比如,alloc方式是对系统内存进行直接操作的,所以会对其做相应的监控,这个就是做了hook的拦截处理(本身也是一个消息机制)。当调用alloc的时候,不是直接走alloc的底层流程,而是先找objc_alloc,找完objc_alloc之后,才开始对下层进行标记;标记完成之后,才开始找alloc方法。
来,回到objc_alloc方法的断点处
然后进入callAlloc方法,就来到callAlloc的实现,当再往下执行一步,就直接到了1940行,中间,并没有进入1932行代码,也没进入1938行代码。
在这里,又是往cls (LGPerson),objc_msgSend(发送)一个alloc消息,那么LGPerson类接收到消息之后,就会执行alloc方法,但是在前面执行objc_alloc方法时,已经标记了,所以这次接收到发送的alloc消息,就直接进入到正常的轨道中,直接调用alloc底层方法流程。
接着往下执行一步断点,那么就进入到了+ (id)alloc 方法里面,开始执行底层的流程了
总结一个流程图 (画得有点菜哈 😄)
这也就是callalloc方法,为什么会执行两次的原因
到了此处,欧耶,大功告成,llvm优化alloc的底层探究,就完成了,有木有点收获啊,(不许没有啊<( ̄▽ ̄)/)
最后再附上一个llvm的github地址:github.com/apple/llvm-…
感谢各位的光临~ ~ ~ ~ ~ ~