前言
上一篇 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
函数,这个函数我们很熟悉,是在慢速查找流程时调用的,也就是说类第一次被使用的时候进行懒加载类
。
下一篇将进行分类加载
的探索,有问题可以评论区多多交流,点个赞支持一下吧!!😄😄😄