前言
上一篇 iOS 底层原理:类的加载原理上 文章中,最后讲到readClass函数走完,仍然没有看到ro和rw的相关操作,今天紧接readClass函数继续进行探索。
准备工作
一、realizeClassWithoutSwift 的引入
先看下SSLPerson类:
@interface SSLPerson : NSObjec
- (void)say1;
- (void)say2;
- (void)say3;
@end
@implementation SSLPerson
+ (void)load
{
NSLog(@"load");
}
- (void)say1
{
NSLog(@"func-----say1");
}
- (void)say2
{
NSLog(@"func-----say2");
}
- (void)say3
{
NSLog(@"func-----say3");
}
@end
我们已经分析到_read_images函数执行完readClass,现在继续向下探索,在源码中添加SSLPerson类的条件控制代码:
运行程序,查看是否可以进入断点:
- 可以看到断点成功进入,现在的
cls就是我们的SSLPerson类。在realizeClassWithoutSwift函数处打断点,接下来断点进入realizeClassWithoutSwift。
二、realizeClassWithoutSwift 分析
ro 中的 baseMethods
断点进入realizeClassWithoutSwift:
通过lldb打印调试:
- 通过打印可以发现,
ro中的方法列表这个时候已经有值了,分别是say4、say1、say2、say3,接下来继续向下探索。
rw、ro 操作
- 这段代码,创建
rw,将ro的值赋给rw,再将rw赋值给类的bits,继续向下进行。
isa 相关处理
- 如果是
MetaClass,会调用setInstancesRequireRawIsa函数进行相关处理。 - 不是
MetaClass时,如果设置了DisableNonpointerIsa的值,也会进行instancesRequireRawIsa的相关处理,上一篇 iOS 底层原理:类的加载原理上 我们有对DisableNonpointerIsa环境变量进行过操作。
setSuperclass、initClassIsa
继续向下进行:
- 这里是递归设置
superClass的继承链,和isa的走位链。
- 又进行了一些相关的设置,就会走到
methodizeClass函数,我们接下来进入methodizeClass进行探索,如图2751行。
三、methodizeClass 分析
断点进入methodizeClass:
- 这里通过
baseMethods()获取了方法列表,如果有值的话,调用prepareMethodLists函数对方法进行处理。
prepareMethodLists
进入prepareMethodLists:
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
}
fixupMethodList
点击进入fixupMethodList:
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
...
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
...
}
- 看代码,
SortBySELAddress是按SEL的地址排序。我们在 OC 原理探索:方法的慢速查找流程 中说到慢速查找流程中的二分查找就是用到了这里排好序的列表,接下来进行验证。
在排序代码前后添加代码,用来打印方法名和地址:
查看打印结果:
- 排序前方法打印顺序是
say4、say1、say2、say3。 - 排序后方法打印顺序是
say1、say4、say2、say3,地址大小是0x100003f81 < 0x100003f8b < 0x100003f90 < 0x100003f95,确实是由小到大排序了。
四、懒加载类与非懒加载类
对上面的流程先总结一下:_read_images -> realizeClassWithoutSwift( rw、ro 等操作) -> methodizeClass -> prepareMethodLists -> fixupMethodList(排序等操作)。
非懒加载类
我们重新回到文章的第一部分,_read_images函数执行完readClass,也就是下面这段代码:
- 我们将
SSLPerson类中的load方法去掉,再次运行程序,发现这里的断点就进不来了。 - 这里就涉及到了
懒加载类与非懒加载类,如果有+ load方法就会进行非懒加载,程序启动的时候,就会进行上面的ro、rw、排序等一系列的操作,非常耗时。
懒加载类
非懒加载类效率很低,如果所有的类都进行非懒加载会非常非常耗时,所以苹果采用了按需加载,也就是懒加载类,用到的时候再进行加载。
非懒加载类具体是什么时候被加载的呢,通过上面的流程,我们可以知道类的加载都会进行rw、ro、排序等操作,也就是realizeClassWithoutSwift,那么我以这个为切入点进行探索。
在main.m中添加SSLPerson类的方法调用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
}
return 0;
}
在realizeClassWithoutSwift中添加SSLPerson类的断点,运行程序:
- 可以成功的断在这里,接下来
bt打印堆栈。
- 看到了
lookUpImpOrForward函数,这个函数我们很熟悉,是在慢速查找流程时调用的,也就是说类第一次被使用的时候进行懒加载类。
下一篇将进行分类加载的探索,有问题可以评论区多多交流,点个赞支持一下吧!!😄😄😄