美文网首页selector
iOS底层之分类的加载原理

iOS底层之分类的加载原理

作者: 大橘猪猪侠 | 来源:发表于2020-10-17 17:20 被阅读0次

    在上一篇文章中探索过iOS类的加载,也探索了部分分类的加载;那么本篇文章将继续接着继续探索分类的加载。

    在上一篇文章中,我们在attachCategories方法中探究到了在分类进行添加之后,才会对rwe进行赋值和初始化的,那么我们就从rwe开始赋值的操作继续探索。

    在探索过程中,我们需要带着问题去探索,第一个问题就是分类时如何加载到类中去的?

    首先看下图,是attachCategories的一部分代码,在rwe初始化之后,rwe就有值了,那么在attachLists是怎么把方法添加到里面去的呢?

    下图是attachLists方法,它分三种情况:
    第一种情况是第二方框,当addedLists来了之后,将addedLists的第一个元素赋值给list,也就是说,当list不存在时,就会从0-1;
    第二种情况是第三个方框的代码,当有list之后,加入很多个list,总的意思就是将新加入的放在前面,旧的数据放在后面。它这样的实现是一种算法思维,LRU算法,也就是说当分类和主类中的方法存在一样时,就加载主类的方法。
    第三种情况是加载多个分类方法,它的功能是先扩容,然后将后加入的分类放在前面。

    iShot2020-10-17 13.53.52.png

    下面我们去通过代码去验证一下:
    首先我们根据rwe的初始化代码去研究,在代码中添加我们对自己的类的判断:
    attachCategories方法中的auto rwe = cls->data()->extAllocIfNeeded();行代码去查看extAllocIfNeeded()方法中的extAlloc

    iShot2020-10-17 14.22.32.png

    当程序进入到这个判断中,就证明是我们自己添加的类;下面我们来看一下list的变化:

    在刚开始的时候,list是空的,没有任何值;

    (lldb) p rwe
    (class_rw_ext_t *) $0 = 0x0000000101130a10
    (lldb) p *$0
    (class_rw_ext_t) $1 = {
      ro = 0x0000000000000000
      methods = {
        list_array_tt<method_t, method_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      properties = {
        list_array_tt<property_t, property_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      protocols = {
        list_array_tt<unsigned long, protocol_list_t> = {
           = {
            list = 0x0000000000000000
            arrayAndFlag = 0
          }
        }
      }
      demangledName = 0x0000000000000000
      version = 0
    }
    
    iShot2020-10-17 14.32.56.png

    而当我们执行完method_list_t *list = ro->baseMethods();行代码后,再去获取list的值:

    (lldb) p list
    (method_list_t *) $2 = 0x00000001000081c8
    (lldb) p *$2
    (method_list_t) $3 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 8
        first = {
          name = "kc_instanceMethod1"
          types = 0x0000000100003f73 "v16@0:8"
          imp = 0x0000000100003aa0 (KCObjc`-[Person kc_instanceMethod1])
        }
      }
    }
    (lldb) 
    

    可以看到它现在加载的是本类的rw;
    下面继续进入到attachLists中去看看是如何进行加载的:

    iShot2020-10-17 14.36.38.png

    当程序进入attachLists方法,它首先执行第一步的方法,将list赋值为addedLists的第1个元素;
    下面我们来看看他的值是什么:

    (lldb) p addedLists
    (method_list_t *const *)【指针地址】 $4 = 0x00007ffeefbf63f8
    (lldb) p addedLists[1]
    (method_list_t *const) $5 = 0x00000001003419dd
    (lldb) p 0x00000001003419dd
    (long) $6 = 4298381789
    (lldb) p *$5
    (method_list_t) $7 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 1936876880
        count = 620785263
        first = {
          name = <no value available>
          types = 0xa0e781a6e89188e6 ""
          imp = 0x20849ae7b6a9e794 (0x20849ae7b6a9e794)
        }
      }
    }
    (lldb) p addedLists[0]
    (method_list_t *const) $8 = 0x00000001000081c8
    (lldb) p *$8
    (method_list_t) $9 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 8
        first = {
          name = "kc_instanceMethod1"
          types = 0x0000000100003f73 "v16@0:8"
          imp = 0x0000000100003aa0 (KCObjc`-[Person kc_instanceMethod1])
        }
      }
    }
    

    addedLists的第0个,第1个都打印了一下,都有值,因为地址是连续的,第一个可能是别人的内容。那么本类的方法就加载完毕,现在去看看分类的加载:

    继续执行方法,回道attachCategories方法,在程序断点执行到下图时:

    iShot2020-10-17 14.51.08.png

    我们去看一下mlist的值(这个如果执行的方法不是这个,可以清除缓存重新试一下),可以看到是分类的方法:

    (lldb) p mlist
    (method_list_t *) $0 = 0x0000000100008038
    (lldb) p *$0
    (method_list_t) $1 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 24
        count = 4
        first = {
          name = "kc_instanceMethod1"
          types = 0x0000000100003f73 "v16@0:8"
          imp = 0x0000000100003970 (KCObjc`-[Person(LGA) kc_instanceMethod1])
        }
      }
    }
    (lldb) 
    

    继续执行程序,看下图,首先经过排序,然后指针内存平移,获得最后一个元素:


    iShot2020-10-17 14.55.52.png
    (lldb) p mlists
    (method_list_t *[64]) $2 = {
      [0] = 0xffffffffffffffff
      [1] = 0x0000000000000000
      [2] = 0x00007ffeefbf6a20
      [3] = 0x00007fff68c52ad3
      [4] = 0x00007fff86a22be0
      [5] = 0x00007ffeefbf6b10
      [6] = 0x00007ffeefbf69e0
      [7] = 0x000000010030f66e
      [8] = 0x000000010034ce08
      [9] = 0x000000010034ce08
      [10] = 0x00007fff86a22be0
      [11] = 0x00007ffeefbf6a8f
      [12] = 0x00007ffeefbf69b0
      [13] = 0x0000000100310a70
      [14] = 0x00007ffeefbf6af8
      [15] = 0x00007ffeefbf6a8f
      [16] = 0x00007ffeefbf6a90
      [17] = 0x00007ffeefbf6af8
      [18] = 0x00007ffeefbf69e0
      [19] = 0x0000000100310a25
      [20] = 0x01007ffeefbf6a00
      [21] = 0x00007ffeefbf6a8f
      [22] = 0x00007ffeefbf6a90
      [23] = 0x00007ffeefbf6af8
      [24] = 0x0000000000000000
      [25] = 0x00007fff8f2bce28
      [26] = 0x0000000000000000
      [27] = 0x00000001003432a2
      [28] = 0x00007ffeefbf6a20
      [29] = 0x00007fff68c22ad7
      [30] = 0x00007fff8f2bce28
      [31] = 0x0000000000000044
      [32] = 0x00007ffeefbf6a50
      [33] = 0x00007fff68c2b2bd
      [34] = 0x00000001000b0e68
      [35] = 0x00019ca683cec01a
      [36] = 0x00000001000b0d28
      [37] = 0x00000001002d77b0
      [38] = 0x00007ffeefbf6b30
      [39] = 0x00007fff68c2941e
      [40] = 0x00000001000dedc0
      [41] = 0x00000001003432db
      [42] = 0x00000001003419dd
      [43] = 0x0000000000000000
      [44] = 0x0000000000000000
      [45] = 0x0000000000000002
      [46] = 0x00007ffeefbf6ab0
      [47] = 0x00000001005b12d5
      [48] = 0x00000001005af8e0
      [49] = 0x00000001005af8e0
      [50] = 0x00007ffeefbf6be0
      [51] = 0x00000001000038f0
      [52] = 0x00007ffeefbf6ae0
      [53] = 0x0000000100008410
      [54] = 0x00007ffeefbf6b00
      [55] = 0x0000000100008430
      [56] = 0x00007ffeefbf6b00
      [57] = 0x00000001002e42ae
      [58] = 0x00000001000083e8
      [59] = 0x0000000100008410
      [60] = 0x00007ffeefbf6b30
      [61] = 0x00000001002e4412
      [62] = 0x0000003000000018
      [63] = 0x0000000100008038
    }
    (lldb) p mlists + ATTACH_BUFSIZ - mcount
    (method_list_t **) $3 = 0x00007ffeefbf6b18
    (lldb) p *$3
    (method_list_t *) $4 = 0x0000000100008038
    (lldb) 
    

    当继续执行,又开始跑回attachLists方法,然后执行第二部操作,其中的hasArray原本是私有属性,经过更改,变为public:

    iShot2020-10-17 15.00.49.png

    而当我们继续执行,还是会重复第二次的操作,接下来就进入了第三种方式的加载,开始是LGA分类的加载,然后是LGB分类的加载:
    list_array_tt的结构

    iShot2020-10-17 15.07.19.png

    执行第三部分代码:


    iShot2020-10-17 15.07.53.png
    (lldb) p array()
    (list_array_tt<method_t, method_list_t>::array_t *) $14 = 0x0000000100640730
    (lldb) p $14.lists[0]
    (method_list_t *) $15 = 0x0000000100008300
      Fix-it applied, fixed expression was: 
        $14->lists[0]
    (lldb) p *$15
    (method_list_t) $16 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "kc_instanceMethod1"
          types = 0x0000000100003f73 "v16@0:8"
          imp = 0x0000000100003be0 (KCObjc`-[Person(LGB) kc_instanceMethod1])
        }
      }
    }
    (lldb) 
    

    打印得到的是LGB的分类方法,最后完整执行程序,最终打印的方法也是我们在上面验证的方法:

    iShot2020-10-17 15.09.56.png

    那么关于如何加载分类,已经得到了答案,那么是在什么时机加载的呢?

    下面带着第二个问题去探索:

    看看完整的执行整个过程(中间加了很多自己写的判断类的代码,有很多打印输出),其中主类和两个分类都添加了load方法:

    readClass: 这个是我要研究的 Person 
    _getObjc2NonlazyClassList: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    methodizeClass: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    attachToClass: 这个是我要研究的 Person 
    load_categories_nolock:operator(): 这个是我要研究的 Person 
    extAlloc:这是我要研究的 Person 
    attachCategories: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    extAlloc:这是我要研究的 Person 
    load_categories_nolock:operator(): 这个是我要研究的 Person 
    attachCategories: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    prepare_load_methods: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    prepare_load_methods: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    2020-10-17 15:13:22.830386+0800 KCObjc[40272:4663341] -[Person(LGB) kc_instanceMethod1]
    2020-10-17 15:13:22.831749+0800 KCObjc[40272:4663341] 0x10115be80
    

    从上面的所有方法的打印过程中,有很多方法都是探索过的,通过上帝视角,得到第二个问题的探索入口在load_categories_nolock方法当中;
    而经过搜索load_categories_nolock后,只有两处调用了此方法,一个在_read_images中,另一个在loadAllCategories方法中,而经过验证,_read_images方法中的load_categories_nolock并不会执行,那么真正执行load_categories_nolock的方法在loadAllCategories中。

    那么在之前的探索,可以得知非懒加载类在Load的情况下才存在,那么如果主类中加入Load方法,分类只弄一个有Load方法,去验证一下分类是否会加载;

    在经过测试后,得到一个结论:只要有一个分类是非懒加载类,那么所有分类方法都会是非懒加载类,运行时会将非懒加载类添加进去,它的数据全部由load_image加载到数据。

    那么就有一个很有意思的问题,当主类没Load方法,分类有或分类没Load,主类有,那又会有哪些情况呢?
    下面去探索一下:
    首先在分类中将Load方法注释,主类保留:
    先完整执行程序,看打印输出的结果

    readClass: 这个是我要研究的 Person 
    _getObjc2NonlazyClassList: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    methodizeClass: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    attachToClass: 这个是我要研究的 Person 
    2020-10-17 16:03:15.569478+0800 KCObjc[40649:4689679] -[Person(LGB) kc_instanceMethod1]
    2020-10-17 16:03:15.570557+0800 KCObjc[40649:4689679] 0x10072a1f0
    Program ended with exit code: 0
    

    可以看到程序会执行realizeClassWithoutSwift方法,我们以此方法为研究对象
    realizeClassWithoutSwift方法中打上断点,去看看ro的值:

    iShot2020-10-17 16.01.43.png
    (lldb) p kc_ro
    (const class_ro_t *) $0 = 0x0000000100008240
    (lldb) p *$0
    (const class_ro_t) $1 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100003f6d "\x11"
      name = 0x0000000100003f66 "Person"
      baseMethodList = 0x0000000100008038
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100008288
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000082d0
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    (lldb) p $1->baseMethodList
    (method_list_t *const) $2 = 0x0000000100008038
      Fix-it applied, fixed expression was: 
        $1.baseMethodList
    (lldb) p $2
    (method_list_t *const) $2 = 0x0000000100008038
    (lldb) p $2.get(0)
    (method_t) $3 = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003be0 (KCObjc`-[Person(LGB) kc_instanceMethod1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(0)
    (lldb) p $2.get(1)
    (method_t) $4 = {
      name = "cateB_2"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003c10 (KCObjc`-[Person(LGB) cateB_2])
    }
      Fix-it applied, fixed expression was: 
        $2->get(1)
    (lldb) p $2.get(2)
    (method_t) $5 = {
      name = "cateB_1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003c40 (KCObjc`-[Person(LGB) cateB_1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(2)
    (lldb) p $2.get(3)
    (method_t) $6 = {
      name = "cateB_3"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003c70 (KCObjc`-[Person(LGB) cateB_3])
    }
      Fix-it applied, fixed expression was: 
        $2->get(3)
    (lldb) p $2.get(4)
    (method_t) $7 = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003980 (KCObjc`-[Person(LGA) kc_instanceMethod1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(4)
    (lldb) p $2.get(5)
    (method_t) $8 = {
      name = "cateA_2"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x00000001000039b0 (KCObjc`-[Person(LGA) cateA_2])
    }
      Fix-it applied, fixed expression was: 
        $2->get(5)
    (lldb) p $2.get(6)
    (method_t) $9 = {
      name = "cateA_1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x00000001000039e0 (KCObjc`-[Person(LGA) cateA_1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(6)
    (lldb) p $2.get(7)
    (method_t) $10 = {
      name = "cateA_3"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003a10 (KCObjc`-[Person(LGA) cateA_3])
    }
      Fix-it applied, fixed expression was: 
        $2->get(7)
    (lldb) p $2.get(8)
    (method_t) $11 = {
      name = "kc_instanceMethod3"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003a80 (KCObjc`-[Person kc_instanceMethod3])
    }
      Fix-it applied, fixed expression was: 
        $2->get(8)
    (lldb) 
    

    可以看到ro中的方法列表有PersonLGALGB的所有方法,而且加载没有顺序,那么意味着当前的数据在这个过程中,只要没有进行非懒加载,在cls读取Mach-o数据的时候,他就已经编译进来了,不需要运行时再添加分类方法进去了。

    然后我们去看看排序之后的方法列表是怎样的:

    (lldb) p $2.get(0)
    (method_t) $12 = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003be0 (KCObjc`-[Person(LGB) kc_instanceMethod1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(0)
    (lldb) p $2.get(1)
    (method_t) $13 = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003980 (KCObjc`-[Person(LGA) kc_instanceMethod1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(1)
    (lldb) p $2.get(2)
    (method_t) $14 = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003ab0 (KCObjc`-[Person kc_instanceMethod1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(2)
    (lldb) p $2.get(3)
    (method_t) $15 = {
      name = "cateA_2"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x00000001000039b0 (KCObjc`-[Person(LGA) cateA_2])
    }
      Fix-it applied, fixed expression was: 
        $2->get(3)
    (lldb) p $2.get(4)
    (method_t) $16 = {
      name = "cateA_1"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x00000001000039e0 (KCObjc`-[Person(LGA) cateA_1])
    }
      Fix-it applied, fixed expression was: 
        $2->get(4)
    (lldb) p $2.get(5)
    (method_t) $17 = {
      name = "cateA_3"
      types = 0x0000000100003f73 "v16@0:8"
      imp = 0x0000000100003a10 (KCObjc`-[Person(LGA) cateA_3])
    }
      Fix-it applied, fixed expression was: 
        $2->get(5)
    (lldb) 
    

    这加载的顺序并没有排序,其实他是进行排序了的;至于它是如何实现排序的,就不写过程了,系统首先会根据主类的方法进行排序,在分类的方法中,则会根据方法地址从小到大进行排序。

    那么就得到了第二个结论:当主类有Load,分类没有,那么数据来自于data(),只需要对同名方法进行处理和修复一下。

    那么主类没有Load,分类也没有Load,先打印一下整个输出:

    readClass: 这个是我要研究的 Person 
    realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 Person 
    realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    methodizeClass: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    attachToClass: 这个是我要研究的 Person 
    2020-10-17 16:29:33.924814+0800 KCObjc[40771:4699799] -[Person(LGB) kc_instanceMethod1]
    2020-10-17 16:29:33.925950+0800 KCObjc[40771:4699799] 0x10114c8d0
    Program ended with exit code: 0
    

    从打印的结果得知,整个数据的加载,都推迟到了第一次调用消息的时候,那么它是从哪加载数据的呢?那么就需要以readClass方法入手,经过探索,它同样是从data()中加载的数据。

    最后一种情况,主类没有Load,分类有Load

    同样的先打印流程:

    readClass: 这个是我要研究的 Person 
    operator(): 这个是我要研究的 Person 
    operator(): 这个是我要研究的 Person 
    prepare_load_methods: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    methodizeClass: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    attachToClass: 这个是我要研究的 Person 
    extAlloc:这是我要研究的 Person 
    attachCategories: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    methodizeClass: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    attachToClass: 这个是我要研究的 Person 
    extAlloc:这是我要研究的 Person 
    attachCategories: 这个是我要研究的 Person 
    prepareMethodLists: 这个是我要研究的 Person 
    prepare_load_methods: 这个是我要研究的 Person 
    realizeClassWithoutSwift: 这个是我要研究的 Person 
    2020-10-17 16:37:55.247587+0800 KCObjc[40892:4706600] -[Person(LGB) kc_instanceMethod1]
    2020-10-17 16:37:55.249469+0800 KCObjc[40892:4706600] 0x101a38cb0
    Program ended with exit code: 0
    

    在所有打印方法中,没有load_categories_nolock方法的打印,那么先在这个方法处打上断点,然后同样的我们先以readClass入手:
    看下图的count只有8个,只有主类的方法,没有分类的方法。

    iShot2020-10-17 16.57.54.png

    继续执行,他就会进入load_categories_nolock方法,通过bt打印堆栈信息:

    iShot2020-10-17 17.02.33.png

    可以看到,它就经过loadAllCategories方法,而调用此方法的函数是load_images方法。这跟第一种方试差不太多;按原理来说,主类没有Load方法,那么它会在第一次消息的时候加载,而实际上是主类没有Load,分类有,会迫使主类提前加载。

    那么最后得到结论

    iShot2020-10-17 17.14.37.png

    相关文章

      网友评论

        本文标题:iOS底层之分类的加载原理

        本文链接:https://www.haomeiwen.com/subject/vzlfmktx.html