上一篇 iOS 底层原理:类的加载原理上 文章中,最后讲到 readClass 函数走完,仍然没有看到 ro rw 的相关操作,今天紧接 readClass 函数继续进行探索。

  • objc4-818.2 源码
  • 一、realizeClassWithoutSwift 的引入

    先看下 SSLPerson 类:

    @interface SSLPerson : NSObjec
    - (void)say1;
    - (void)say2;
    - (void)say3;
    @implementation SSLPerson
    + (void)load
        NSLog(@"load");
    - (void)say1
        NSLog(@"func-----say1");
    - (void)say2
        NSLog(@"func-----say2");
    - (void)say3
        NSLog(@"func-----say3");
    

    我们已经分析到_read_images函数执行完readClass,现在继续向下探索,在源码中添加SSLPerson类的条件控制代码:

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

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

    ro 中的 baseMethods

    断点进入realizeClassWithoutSwift

    通过lldb打印调试:

  • 通过打印可以发现,ro中的方法列表这个时候已经有值了,分别是say4say1say2say3,接下来继续向下探索。
  • 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 原理探索:方法的慢速查找流程 中说到慢速查找流程中的二分查找就是用到了这里排好序的列表,接下来进行验证。
  • 在排序代码前后添加代码,用来打印方法名和地址:

    查看打印结果:

  • 排序前方法打印顺序是say4say1say2say3
  • 排序后方法打印顺序是say1say4say2say3,地址大小是0x100003f81 < 0x100003f8b < 0x100003f90 < 0x100003f95,确实是由小到大排序了。
  • 四、懒加载类与非懒加载类

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

    非懒加载类

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

  • 我们将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类的断点,运行程序:

  • 可以成功的断在这里,接下来bt打印堆栈。
  • 看到了lookUpImpOrForward函数,这个函数我们很熟悉,是在慢速查找流程时调用的,也就是说类第一次被使用的时候进行懒加载类
  • 下一篇将进行分类加载的探索,有问题可以评论区多多交流,点个赞支持一下吧!!😄😄😄

    分类:
    iOS
  •