iOS 底层探索篇 ——类的加载原理(下)
1. 为什么要有ro,rw,rwe
- ro属于cleanmemory,在编译即确定的内存空间,只读,加载后不会改变内容的空间
- rw属于dirtymemory,rw是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存;
- rwe相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe;
ro被复制一份到rw里面,这是因为在运行时分类可以添加方法,而程序员也可以动态添加方法或者属性到类里面,而ro是只读的,所以需要在rw里面来追踪这些东西。
不是每一个类都会动态添加,所以如果这片内存写在rw里面,那么就会对脏内存有影响,所以把这些东西放在rwe里面。
2. cls->data()
realizeClassWithoutSwift中的ro时怎么来的呢?查找到赋值的地方。


发现返回的是一个地址指针,而指针可以进行取值和强转。这里是从macho里面获取地址指针,然后对class_ro_t的数据进行赋值。
3. extAllocIfNeeded & attachCategories
回到methodizeClass里面查看,看到rwe则是等于rw->ext()。


点进去查看实现,发现下面一个方法extAllocIfNeeded,如果有这个方法就会有rwe。

在源码中查找extAllocIfNeeded,发现在attachCategories里面有调用。也就是在添加分类的时候,rwe是必然有值的。同时在class_setVersion,addMethods_finish,class_addProtocol,_class_addProperty,objc_duplicateClass的时候也是有调用的。这说明在运行时进行处理的时候,才会进行rwe的创建。




现在来看attachCategories,因为需要研究分类时如何写到类里面的。在源码中搜索attachCategories,发现在attachToClass,load_categories_nolock里面有调用。


load_categories_nolock比较陌生,而attachToClass在methodizeClass里面有看到过,所以来看一下attachToClass。
看到这里有一个地方判断previously来看是否进入调用attachToClass。
看一下previously是哪里来的。发现是methodizeClass的第二个参数。

接下来搜索methodizeClass的调用。发现在realizeClassWithoutSwift里面。

又看到previously是realizeClassWithoutSwift的第二个参数。

接下来寻找realizeClassWithoutSwift的调用。发现传的第二个参数全是nil,那么就代表previously是一个备用参数,为了方便内部进行相关的调节。
既然previously是nil,那么上面点函数就不会进去,也就是只会调用下面点attachToClass。

接下来看attachCategories里面的attachLists。先看中间最短的地方。这里表示如果没有list而且attachLists里面就一个元素,那么list就是一个只有这个元素的一位数组。

再来看下面的else 的情况,这里做的是重新创建一个count为新list.count + 1 的数组(有oldlist的情况下,否则为0),将之前的一整个数组放到新数组的最后一位,然后将要添加的数组的元素从新数组的头部开始依次添加进去。


再来看最后一个部分,这里做的就是重新创建一个count为oldList的count个数加addedList的count个数的数组,先将oldList的元素依次从i+addedCount位添加进去,然后将addedList的依次从头开始添加进去。


4.分类的懒加载
之前说到类有懒加载并且由load方法来控制,那么分类是否也有懒加载呢?并且分类的懒加载和类的懒加载是否有关系呢?来探索一下。
类和分类都实现load
先来看类和分类都实现load的情况,在attachCategories打下断点并运行。

运行后发现会来到_read_images的非懒加载区域

然后来到realizeClassWithoutSwift。

然后来到methodizeClass里面调用attachToClass。

在往下就来到了attachCategories

所以调用流程如下:
map_images -> map_images_nolock -> _read_images -> readClass -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists
类实现load和分类不实现load
把分类的load方法去掉后重新运行,发现不会实现attachCategories方法了。

类不实现load和分类实现load
把分类的load方法去掉后重新运行,发现和上面的情况一样不会实现attachCategories方法了。

类和分类都不实现load
运行后发现直接到main函数里面了。
5. 4种不同情况下的分类加载
类和分类都实现load
实现类和分类的load方法后运行,进入realizeClassWithoutSwift后输入ro查看分类数据是否被加载进去,如果有的话那么就说明在realizeClassWithoutSwift分类就已经被加载进去了,否则就在realizeClassWithoutSwift之后。
输出所有的baseMehtods之后发现,没有分类的方法,那么说明分类还没有被加载进去。



接下来看load_categories_nolock里面,输出cat看一下,确实是我们创建的分类。

在输出cls 看一下发现确实是LGPerson。

接下来往下走发现调用attachCategories的地方,这里因为cls已经在read_images时候加载过了所以cls->isRealized()为true。

看到attachCategories里面,看到mlist的数据结构,且里面count为2,也就是分类里面的2个方法。

在往下走,发现把mlist放到了mlists里的最后一位。




往下走,跳过中间的属性和协议,就来到了这里。看到这里调用prepareMethodLists进行了排序,然后往rwe的methods调用attachLists添加了分类方法。

进去看attachLists。这里看到addedLists是个二级指针。

然后往下走,看到进入了else。
打印输出发现存入的都是指针。

输出看一下发现这里的addedList[0]存的是分类

类不实现load,分类实现load
没有进入attachCategories,也就是没有加载分类,但是加载类。

这里类被迫营业了。分类是针对主类实现的,所以分类要实现,就要先实现类的。这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data()。




类实现load,分类不实现load
没有进入attachCategories,也就是没有加载分类,但是加载类。

这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data(),

类和分类都不实现load
运行后发现直接到main函数里面了,两者都懒加载。

这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data(),

也就是说,当类和分类都不实现load的时候,两者都懒加载,在类第一次收到消息的时候初始化。但是类和分类加载都已经在data里面了。
6.多个分类的加载情况
分类全都实现load

添加一个分类,然后运行。看到两个分类都加载了进去。


接下来的流程和单个分类的加载情况一样,但是attachLists却不一样了。这里就加载了分类。

分类不全都实现load
运行后还是全都加载了。

processCatlist是block,在下面调用。

load_categories_nolock是由loadAllCategories调用,loadAllCategories则是由load_images里面调用,并且由didInitialAttachCategories和didCallDyldNotifyRegister决定调不调用,didInitialAttachCategories默认为false,而当loadAllCategories调用后,didInitialAttachCategories就为true,所以就只调用一次。


所以能加载到分类的原因是通过catList从macho里面读取到的。
一般来说,mach会加载整个数据结构,当调用load的时候,就会打乱算法来进行计算,所以说load耗时。正常来说,类和分类都会在macho里面加载好的了。
本文深入探讨了iOS中类的加载原理,包括ro、rw、rwe的作用,类数据结构的解析,以及分类的懒加载机制。详细分析了不同情况下分类加载的流程,包括类和分类实现或不实现`load`方法的影响,并研究了多个分类加载的情况。此外,还揭示了分类加载与类加载的关系以及Mach-O文件在其中的角色。
479

被折叠的 条评论
为什么被折叠?



