iOS 底层原理:类的加载原理中

707 阅读4分钟

前言

上一篇 iOS 底层原理:类的加载原理上 文章中,最后讲到readClass函数走完,仍然没有看到rorw的相关操作,今天紧接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类的条件控制代码:

image.png

运行程序,查看是否可以进入断点:

image.png

  • 可以看到断点成功进入,现在的cls就是我们的SSLPerson类。在realizeClassWithoutSwift函数处打断点,接下来断点进入realizeClassWithoutSwift

二、realizeClassWithoutSwift 分析

ro 中的 baseMethods

断点进入realizeClassWithoutSwift

image.png

通过lldb打印调试:

image.png

  • 通过打印可以发现,ro中的方法列表这个时候已经有值了,分别是say4say1say2say3,接下来继续向下探索。

rw、ro 操作

image.png

  • 这段代码,创建rw,将ro的值赋给rw,再将rw赋值给类的bits,继续向下进行。

isa 相关处理

image.png

  • 如果是MetaClass,会调用setInstancesRequireRawIsa函数进行相关处理。
  • 不是MetaClass时,如果设置了DisableNonpointerIsa的值,也会进行instancesRequireRawIsa的相关处理,上一篇 iOS 底层原理:类的加载原理上 我们有对DisableNonpointerIsa环境变量进行过操作。

setSuperclass、initClassIsa

继续向下进行:

image.png

  • 这里是递归设置superClass的继承链,和isa的走位链。

image.png

  • 又进行了一些相关的设置,就会走到methodizeClass函数,我们接下来进入methodizeClass进行探索,如图2751行。

三、methodizeClass 分析

断点进入methodizeClass

image.png

  • 这里通过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);
    }
    ...
}

在排序代码前后添加代码,用来打印方法名和地址:

image.png

查看打印结果:

image.png

  • 排序前方法打印顺序是say4say1say2say3
  • 排序后方法打印顺序是say1say4say2say3,地址大小是0x100003f81 < 0x100003f8b < 0x100003f90 < 0x100003f95,确实是由小到大排序了。

四、懒加载类与非懒加载类

对上面的流程先总结一下:_read_images -> realizeClassWithoutSwift( rw、ro 等操作) -> methodizeClass -> prepareMethodLists -> fixupMethodList(排序等操作)

非懒加载类

我们重新回到文章的第一部分,_read_images函数执行完readClass,也就是下面这段代码:

image.png

  • 我们将SSLPerson类中的load方法去掉,再次运行程序,发现这里的断点就进不来了。
  • 这里就涉及到了懒加载类非懒加载类,如果有+ load方法就会进行非懒加载,程序启动的时候,就会进行上面的rorw排序等一系列的操作,非常耗时。

懒加载类

非懒加载类效率很低,如果所有的类都进行非懒加载会非常非常耗时,所以苹果采用了按需加载,也就是懒加载类,用到的时候再进行加载。

非懒加载类具体是什么时候被加载的呢,通过上面的流程,我们可以知道类的加载都会进行rw、ro排序等操作,也就是realizeClassWithoutSwift,那么我以这个为切入点进行探索。

main.m中添加SSLPerson类的方法调用:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SSLPerson *person = [SSLPerson alloc];
    }   
    return 0;
}

realizeClassWithoutSwift中添加SSLPerson类的断点,运行程序:

image.png

  • 可以成功的断在这里,接下来bt打印堆栈。

image.png

  • 看到了lookUpImpOrForward函数,这个函数我们很熟悉,是在慢速查找流程时调用的,也就是说类第一次被使用的时候进行懒加载类

下一篇将进行分类加载的探索,有问题可以评论区多多交流,点个赞支持一下吧!!😄😄😄