美文网首页iOS学习
iOS-底层原理15-类的加载下

iOS-底层原理15-类的加载下

作者: 一亩三分甜 | 来源:发表于2020-12-20 20:36 被阅读0次

《iOS底层原理文章汇总》

上一篇文章《iOS-底层原理14-类的加载中》介绍了类以什么样的数据格式加载到内存中,本文主要分析什么时机加载分类和类中的方法,以及类和分类中的方法是怎么加载到内存中的。

1.我们都知道类中的地址总共由4段组成,ISA,super_class,cache,bits。

objc_class@2x.png

readClass中,慢慢的从MachO文件中读取内存地址,整个内存连续,会从MachO文件中读取很多地址,为嘛bits里面的数据没有呢?打印出来的地址全为0x0000000000000000呢,为什么还能cls->data()呢?相当于null.data()了。我们从readClass-->realizeClassWithoutSwift中分别打印cls的内存地址为空。bits里面为空,从里面取data()数据也为空,为嘛代码里面仍然可以这么取而没有报错呢。


cls的内存地址@2x.png
(lldb) p/x cls
(Class) $0 = 0x0000000100003340
(lldb) x/4gx 0x0000000100003340
0x100003340: 0x0000000100003318 0x0000000100194140
0x100003350: 0x000000010018e420 0x0000000000000000
readClass: 这个是我要研究的 LGPerson 
_getObjc2NonlazyClassList: 这个是我要研究的 LGPerson 
(lldb) x/4gx cls
0x100003340: 0x0000000100003318 0x0000000100194140
0x100003350: 0x000000010018e420 0x0000000000000000

x/4gx cls读取的是内存情况,当前cls内存还未分配完善和完毕,有指针,指针的分配,地址分配,在内存中都已经分配完善,因为类是唯一的,指针情况唯一且分配完善由编译器帮忙处理,在MachO文件中可以查看,此时读取bits的指针地址

cls->data()@2x.png
(lldb) p/x (class_data_bits_t *)0x0000000100003340
(class_data_bits_t *) $2 = 0x0000000100003340
(lldb) p *$2
(class_data_bits_t) $3 = (bits = 4294980376)

为空的原因是,当前的内存还没有完善,所以为空,可以通过地址指针去识别instancesRequireRawIsa是初始化isa的必要条件

inline void 
objc_object::initClassIsa(Class cls)
{
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}
if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }

如果是nonpointerisa,以16进制打印isa最后一位内存地址为1,如果不是,则最后一位内存地址为0,在前面文章《iOS-底层原理13-类的加载上》验证过

OBJC_DISABLE_NONPOINTER_ISA@2x.png nonpointerisa.png 非nonpointerisa.png

cls中的内存地址经过cls->setInstanceSize(ro->instanceSize),bits的值发生了变化,对data里面的值进行了处理

setInstanceSize@2x.png
经过cls->setHasCxxDtor()之后,cls的内存地址继续发生变化
setHasCxxDtor@2x.png
断点放开到main函数中,查看cls内存地址为0x0000a02400000000,那这个a是什么时候加进去的呢?
cls的bits内存情况@2x.png

2.类和分类中的方法是怎么加载到内存中的,数组分为三种情况,将旧数组往后移,新加入的数组插入到前面,为什么不追加到最后面呢?进入attachLists方法中

attachLists@2x.png

现有的list已经完备,添加的list对于用户的价值高,所以添加到前面,若添加的list价值低,则没必要添加或等到要用的时候再添加,属于LRU算法,实质上添加的list为分类方法,越往后添加的分类方法越在前面

  1. 0 list -> 1 lists,若list为空,且只需添加一个方法,直接添加
  2. 1 list -> many list,假设要添加的addedLists中方法数量为3,newCount = 4,开辟一个新的容量数组,数组的长度为4,将之前数组中的方法移到最后,新添加的数组中的成员从前往后插入,memcpy,表示从什么位置开始,放什么,放多大
    显然此处是从lists的开头开始,放入addLists中的每一个元素,总共放元素的数量*元素的大小


    list->many lists@2x.png
  3. many lists -> many lists,memmove整段平移,假设list中已经存在4个元素了,插入4个元素,开辟新的数组newCount = 4 + 4 = 8,将旧的数组中的元素移到最后,新的数组添加到前面,memmove先将旧的数组移到新开辟的数组后面,memcopy再将addedLists中的元素添加到数组开头


    manylists->manylists@2x.png

3.通过代码调试本类的方法,分类的方法加载到类中的过程

    1. realizeClassWithoutSwift方法进入到methodizeClass方法后,将ro中的本类方法先按照sel地址的大小进行排序prepareMethodLists,排序前后list中的方法顺序进行对比,排序前list是按照在文件中的书写顺序进行排列,如下
LGPerson.h@2x.png
LGPerson.m@2x.png
类中方法排序前@2x.png

排序后ro中的方法列表顺序,prepareMethodList->fixupMethodList,为什么instanceMethod3在instanceMethod2前面???

fixupMethodList方法排序后@2x.png Method3在Method2的前面@2x.png

这儿的方法的排序fixupMethodList指明为sort by selector address即通过sel的地址来进行排序,并不是根据sel中的name字符串的大小来排序,我们来看排序后的mlist中的8个方法的地址大小,发现依次从小到大排列

SortBySELAddress@2x.png

我们先看排序前mlist中的8个方法sel的地址大小,以method_t结构的形式存储在数组中,method_t由三部分组成SEL name,const char *types,MethodListIMP imp分别占用8个字节,一共24个字节,打印出来的mlist中的元素的内存地址每个相隔24个字节的大小

此时我们先打印下mlist中每一个元素的地址,数组中的地址是连续的,在开辟内存空间后不会变化,

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
}
mlist排序前元素地址相差24字节@2x.png

排序mlist中的元素,实质上并不是比较元素地址的大小,而是比较数组中的元素的name的指针地址的大小,并按照从小到大的顺序进行排列

SortBySELAddress从小到大排序@2x.png
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

每一个方法name的内存地址,发现kc_instanceMethod3的内存地址为0x0000000100001d43,大于kc_instanceMethod1的内存地址0x0000000100001cf0,需要交换顺序,其他顺序不变,我们执行排序方法

method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);

后,查看是否和预期一致,我们发现mlist数组中的元素地址未发生变化,首元素已经变为kc_instanceMethod1,不再是kc_instanceMethod3,说明排序生效,我们继续打印排序后的每一个name的地址验证


mlist中元素name的sel地址排序生效@2x.png 方法排序by sel address@2x.png

由此可以验证方法的排序是按照元素中方法的name的sel地址进行排序,方便后面方法查找过程中进行二分查找

(lldb) p/x $10.get(0).name
(SEL) $19 = 0x0000000100001d43 "kc_instanceMethod3"
(lldb) p/x $10.get(1).name
(SEL) $20 = 0x0000000100001cf0 "kc_instanceMethod1"
(lldb) p/x $10.get(2).name
(SEL) $21 = 0x0000000100001d56 "kc_instanceMethod2"
(lldb) p/x $10.get(3).name
(SEL) $22 = 0x0000000100001d69 "kc_name"
(lldb) p/x $10.get(4).name
(SEL) $23 = 0x0000000100001d71 "setKc_name:"
(lldb) p/x $10.get(5).name
(SEL) $24 = 0x0000000100001d7d "kc_age"
(lldb) p/x $10.get(6).name
(SEL) $25 = 0x0000000100001d84 "setKc_age:"
(lldb) p/x $10.get(7).name
(SEL) $26 = 0x00007fff73718b90 ".cxx_destruct"
排序前method_list_t中第一个方法kc_instanceMethod3@2x.png
mlist中元素的name的sel地址比较@2x.png
  • 2.分为两步,第一步,加载本类中的方法到类中,此步又分为两步,第一步为添加类方法到元类中,第二步添加对象方法到类中
  • I.第一步添加类方法到LGPerson元类中,realizeClassWithoutSwift方法是一个递归循环,循环添加的是类方法+ (void)kc_sayClassMethod;,从realizeClassWithoutSwift-->metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil)-->methodizeClass(cls, previously)-->objc::unattachedCategories.attachToClass-->attachCategories(cls, list.array(), list.count(), flags)-->rwe = cls->data()->extAllocIfNeeded()-->class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)-->rwe->methods.attachLists(&list, 1),成功添加后,类中的内容开始变化
    添加类方法到类中@2x.png
类方法数量为1个@2x.png

添加完成后,查看rwe中的内容

类方法存储在rwe中@2x.png
  • II.添加对象方法到类中,此时的rwe为NULL并没有初始化,并不能对本类中的方法进行添加,程序继续往下执行进入objc::unattachedCategories.attachToClass方法进入attachCategories方法,有类方法走两次,没有类方法走入一次,类方法添加到元类中,对象方法添加到本类中,此时LGPerson中的对象方法一共有8个,包含set和get方法,进入auto rwe = cls->data()->extAllocIfNeeded()中初始化rwe,从而rwe->methods.attachLists(&list, 1)
attachCategories添加类方法和对象方法@2x.png rwe为NULL无法添加@2x.png rwe为NULL@2x.png
  • 3.attachCategories中,先对rwe进行初始化rwe = cls->data()->extAllocIfNeeded(),将实例方法添加到rwe中rwe->methods.attachLists(mlists, mcount),我们断点调试看看methods.attachLists(mlists,mcount)方法中具体做了什么
extAllocIfNeeded@2x.png
extAlloc@2x.png

要添加的实例方法的数量为8个,addedLists((method_list_t *const *) $19 = 0x00007ffeefbf8b28)数组二维数组,8个实例方法一维数组指针为addedLists[0],那么当前有第二个元素吗?此时addedLists[1]并不为空,因为数组中的地址是连续的,整个内存段是连续的,可能存放的是别人的地址

addLists[0]为一维数组@2x.png addLists@2x.png

本类的方法加载完毕,后面开始加载分类中的方法

rwe中attach存储本类中的实例方法@2x.png 本类方法加载完毕@2x.png

4.分类中方法的加载,LGPerson中存在两个分类LGPerson(LGA)和LGPerson(LGB),首先对分类中的数据进行处理,kc_instanceMethod1来自于分类LGPerson(LGA)中,数组长度数量为2,两个分类数据都处理,对分类中的方法按照name的SEL地址进行排序prepareMethodLists,后添加在rwe中

对分类数据进行处理@2x.png
两个分类@2x.png

给二维数组前面预留两个位置array->lists[0]和array->lists[1],每个位置插入一个一维数组,addedLists[0]或addedList[1]...,每个一位数组addedList[i]中包含4个元素,即分类中的4个实例方法,LGPerson(LGB)的4个方法组成的一维数组addedLists[0]在第一个位置array->lists[0],LGPerson(LGA)的4个实例方法组成的一维数组addedLists[1]在第二个位置array->lists[1]。

5.所以如果本类和分类中有同名的方法如kc_instanceMethod1,调用的会是最后编译的分类中的方法,因为最后编译的分类中的方法在二维数组的第一个位置array->lists[0],也就是顶端。

分类中添加方法二维数组预留两个位置@2x.png
分类中方法添加到类中@2x.png

6.LGPerson(LGA)和LGPerson(LGB)分别存在类方法+(void)load方法,是什么时候关联到类中的?已经存在一个方法kc_sayClassMethod,添加两个+(void)load方法,realizeClassWithoutSwift方法会传入cls->ISA进入循环遍历

元类中添加对象的类方法@2x.png
元类中添加类的类方法@2x.png

先开辟rwe内存空间,添加类方法kc_sayClassMethod到元类的rwe中

添加类方法到元类的rwe中@2x.png 元类rwe中添加类方法@2x.png 分类中的load方法处理@2x.png LGPerson(LGB)的load方法@2x.png

和前面对象方法的添加是一样的,开辟新的二维数组内存空间,将已经存在的存放kc_sayClassMethod方法的oldList位置移到二维数组末尾,二维数组前两位预留,newCount(3) = oldCount(1) + addedCount(2),array()->lists[0]和array()->lists[1],存放LGPerson(LGA)和LGPerson(LGB)中的load方法,分类中的两个类方法+(void)load均添加到元类中。

类方法添加@2x.png 分类中的两个+(void)load方法均添加到元类中@2x.png

本类和分类中同名kc_instanceMethod1的方法调用,会优先调用后编译的分类中的方法

调用kc_instanceMethod1方法.png

分类中的load方法添加后数组排列情况

分类中的load方法添加后数组排列情况@2x.png

以上分析的是两个分类LGPerson(LGA)和LGPerson(LGB)中均写了load方法,但主类LGPerson中并没有写load方法,下面分析分类和主类中均写load方法的情况,与主类中不写load方法唯一的区别就是attachLists方法的时候是一个一个加载,先加载主类的,后加载分类的,按LGPerson->LGPerson(LGA)->LGPerson(LGB)顺序加载,在attachList方法中第一次加载本类LGPerson中方法走else if(!list && addCount == 1),第二次加载分类LGPerson(LGA)走else 1 list -> many lists,第三次加载分类LGPerson(LGB)走hasArray()

主类中写load方法挨个加载.png
预留头部位置array()->lists[0],
将二维数组中的第一个array()->list[0]和第二个
array()->list[1],分别存放LGPerson(LGA)和
LGPerson中的方法的一维数组位置整体前移一位,
移动到array()->lists[1]和array()->lists[2],
预留二维数组头部的一个位置array()->lists[0],
从而存放LGPerson(LGB)中的实例方法
类和分类均实现load方法加载.png
类和分类中的方法加载完成后对比.png
放开断点,发现最后执行的对象方法还是分类LGPerson(LGB)中的-[LGPerson(LGB) kc_instanceMethod1]同名方法的调用最后还是最后加载的分类LGPerson(LGB)中的实例方法
同名方法的调用最后还是最后加载的分类LGPerson(LGB)中的实例方法.png

已经明确了第三步类和分类中的方法怎么加载到类中的,接下来分析,什么时机加载到类中?

1.将主类和分类中都实现load方法,查看方法的加载顺序,那么问题来了load_categories_nolock这个方法是在什么时候调用的呢?搜一下发现在两个方法中会调用attachCategories,一个是attachToClass,一个是load_categories_nolock,attachToClass中的attachCategories调用除非类加载两次时才会调用,一般只会加载一次,不会进入,所以会调用load_categories_nolock方法,

attachToClass方法调用attachCategories方法@2x.png

再次搜load_categories_nolock方法的调用,发现有两处会调用,一处是_read_images,一处是loadAllCategories,在read_images中打断点发现并不会进入,而会进入loadAllCategories方法的断点,从而进入load_categories_nolock方法,可以通过断点堆栈查看


attachCategories堆栈@2x.png
readClass: 这个是我要研究的 LGPerson 
_getObjc2NonlazyClassList: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
load_categories_nolock:operator(): 这个是我要研究的 LGPerson 
extAlloc: 这个是我要研究的 LGPerson 
attachCategories: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
extAlloc: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
load_categories_nolock:operator(): 这个是我要研究的 LGPerson 
attachCategories: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
prepare_load_methods: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
prepare_load_methods: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
2020-12-16 01:00:02.841211+0800 KCObjc[38879:2254909] -[LGPerson(LGB) kc_instanceMethod1]
load_categories_nolock.png
load_images->loadAllCategories.png
loadAllCategories->load_categories_nolock.png

主类和每一个分类都实现了load方法加载顺序如下:

类加载调用顺序@2x.png

主类和两个分类中的一个实现了load方法:只要有一个分类是非懒加载分类(实现了load方法),没有实现load方法的分类的加载和实现了load方法的分类的加载是一模模一样样,所有都会是非懒加载分类,为什么会是这样呢?苹果做了这一层设计可以理解,遍历循环开辟rwe空间,如果是懒加载的分类,又得重新计算重新开辟重新处理,既然都是关于LGPerson的分类,为什么不一次性加载完呢。

attachCategories分类load方法只实现一个@2x.png
分类load方法只实现一个@2x.png

一共有四种情况:

  • 1.主类实现load方法,分类实现load方法 ----- 已探索 全部都走,到load_image中加载数据
  • 2.主类实现load方法,分类未实现load方法 数据来源于data()
  • 3.主类未实现load方法,分类实现load方法 ----- 已探索
  • 4.主类未实现load方法,分类未实现load方法

2.主类实现load方法,分类均未实现load方法

加载顺序如下:

readClass: 这个是我要研究的 LGPerson 
_getObjc2NonlazyClassList: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
2020-12-17 01:41:28.915507+0800 KCObjc[68663:2631620] -[LGPerson(LGB) kc_instanceMethod1]
主类实现分类未实现load方法@2x.png

一共连同类和分类中存在16个方法一起加载到类中,在realizeClassWithoutSwift中未排序前,方法随意的排列,ro在cls读取MachO文件数据的时候已经有了,方法已经编译进去ro了,不需要运行时再添加方法进入ro了,程序进入methodizeClass方法对16个方法按照sel的内存地址进行排序prepareMethodLists

方法未排序前@2x.png

方法排序后的顺序,文章开头我们已经发现,方法的排序是根据方法中的name的sel地址大小进行排序,前提是name是不同名的方法,那么类和分类中同名的方法怎么排序的呢,同名的方法的name完全相同,按照imp的name的字符串大小排序
(KCObjc`-[LGPerson(LGB) kc_instanceMethod1]) > (KCObjc`-[LGPerson(LGA) kc_instanceMethod1]) > (KCObjc`-[LGPerson kc_instanceMethod1])

类和分类中方法排序@2x.png

同名的方法如果按照之前的方式比较name的sel内存地址,会发现相等,此时按照imp中字符串的大小进行排序

先了解下Sel和Imp的关系

SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。

IMP:一个函数指针,保存了方法的地址

IMP和SEL关系

每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table. Dispatch table是一张SEL和IMP的对应表
也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法

由此可以得出数组中保存的元素结构体method_t中的name保存的是方法的编号,所以同名的方法编号一致,进而存放的内存地址一致,进而排序是无法比较大小,方法编号name一致但对应的函数指针Imp不一致(由不同的类或分类实现),此时针对同名方法可以比较Imp中对应的字符串的大小

(KCObjc`-[LGPerson(LGB) kc_instanceMethod1]) > (KCObjc`-[LGPerson(LGA) kc_instanceMethod1]) > (KCObjc`-[LGPerson kc_instanceMethod1])

同名方法排列顺序@2x.png

非同名的方法的Imp的内存地址也是从小到大排列,但非同名的方法的排序方式还是按照name的sel内存地址进行排序的

同名的方法排序@2x.png
imp地址从小到大排列@2x.png 比较的是name的内存地址@2x.png

综上,主类实现load方法,分类均未实现load方法,方法的数据来源于data()

4.主类未实现load方法,分类未实现load方法,方法的数据来源于第一次消息的时候,方法的数据来源于data()

  • 1.加载顺序如下
readClass: 这个是我要研究的 LGPerson 
realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
2020-12-20 15:16:45.927167+0800 KCObjc[12064:451377] -[LGPerson(LGB) kc_instanceMethod1]
  • 2.ro直接从data中读取方法
ro从data中读取数据@2x.png

5.主类未实现load方法,分类实现了load方法,迫使主类提前加载

  • 1.加载顺序如下
readClass: 这个是我要研究的 LGPerson 
load_categories_nolock:operator(): 这个是我要研究的 LGPerson 
load_categories_nolock:operator(): 这个是我要研究的 LGPerson 
prepare_load_methods: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
extAlloc: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
methodizeClass: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
attachToClass: 这个是我要研究的 LGPerson 
extAlloc: 这个是我要研究的 LGPerson 
attachCategories: 这个是我要研究的 LGPerson 
prepareMethodLists: 这个是我要研究的 LGPerson 
prepare_load_methods: 这个是我要研究的 LGPerson 
realizeClassWithoutSwift: 这个是我要研究的 LGPerson 
2020-12-20 15:45:12.649529+0800 KCObjc[12688:467046] -[LGPerson(LGB) kc_instanceMethod1]
  • 2.readClass断点调试,发现先从data()中获取本类8个方法,后进入load_images->loadAllCategories->load_categories_nolock->attachCategories加载分类数据
LGPerson本类中的8个方法@2x.png
load_categories_nolock堆栈@2x.png
  • 3.分类实现了load方法,迫使主类提前加载load_images-->loadAllCategories-->load_categories_nolock-->objc::unattachedCategories.addForClass-->prepare_load_methods-->realizeClassWithoutSwift(cls, nil)


    非懒加载分类迫使主类去加载@2x.png

    两个分类LGPerson(LGA)和LGPerson(LGB),数组中有两个元素,传入数组,先去开辟rwe空间,将排序好的主类的方法添加后,for循环两次将数据准备好放入64个大小数组的最后两位,之后传入数组指针到attachLists将分类中的方法添加,此过程在文章开头已经分析


    传入两个分类@2x.png
    添加分类方法@2x.png
  • 4.类和分类搭配加载

类和分类搭配加载@2x.png

类和分类加载四种情况绘制的流程图如下

类的加载下.png

相关文章

网友评论

    本文标题:iOS-底层原理15-类的加载下

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