iOS 底层探索篇 ——类的加载原理(下)
- 1. 为什么要有ro,rw,rwe
- 2. cls->data()
- 3. extAllocIfNeeded & attachCategories
- 4.分类的懒加载
- 类和分类都实现load
- 类实现load和分类不实现load
- 类不实现load和分类实现load
- 类和分类都不实现load
- 5. 4种不同情况下的分类加载
- 类和分类都实现load
- 类不实现load,分类实现load
- 类实现load,分类不实现load
- 类和分类都不实现load
- 6.多个分类的加载情况
- 分类全都实现load
- 分类不全都实现load
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里面加载好的了。