Q1:为什么要有ro、rw、rwe
- 沙盒中的数据,ro是readOnly,所以需要改变进行copy,变成rw
- ro(class_ro_t) -> clean memory是指加载后不会发生更改的内存,它是只读的。可以从内存中移除,需要时再从磁盘加载进来。
- rw(class_rw_t) -> dirty memory是指在进程运行时会发生更改的内存,它是可读可写的。类结构一经使用就会变成dirty memory,因为运行时会向它写入新的数据。rw相对于iOS是非常昂贵,程序运行就一直存在。
- rw,从ro复制过来,脏内存、昂贵,存储类的父类、名字
- rwe,rw的扩展extension,运行时动态添加,存储类的方法、协议、属性,当前类有一个标识,ext,代表有扩展
Q2:cls->data()来源
- realizeClassWithoutSwift中的ro为(const class_ro_t *)cls->data(),
cls->data()引申调用到bits.data()
class_rw_t *data() const {return bits.data();}
bits.data()是通过bits & mask 得到地址指针返回
class_rw_t* data() const {return (class_rw_t *)(bits & FAST_DATA_MASK);}
得到了地址指针,按class_ro_t结构体类型强转后可以按其内存结构依次赋值
struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;#ifdef __LP64__uint32_t reserved;#endif...}
Q3: rwe什么时候赋值的
extAllocIfNeeded -> rwe,extAllocIfNeeded什么时候调用呢?
attachCategories
rwe取值取决于extAllocIfNeed,分类在什么时候加载取决于attachCategories
全局搜索attachCategories,调用其有两处
0 lists -> 1 list, 一维,list指针指向添加数组的首地址
- 1 list -> many lists,二维,根据oldCount和addedCount大小开辟空间,将添加的数组元素插入在新开辟空间的头部,oldList作为一个整体添加在新开辟空间尾部
many lists -> many lists,二维,根据oldCount和addedCount大小开辟空间,先倒叙排列,将上个步骤的oldList和addedCount依次插入在新开辟空间的尾部,再插入新的added内容
void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) {// many lists -> many listsuint32_t oldCount = array()->count;uint32_t newCount = oldCount + addedCount;array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));newArray->count = newCount;array()->count = newCount;for (int i = oldCount - 1; i >= 0; i--)newArray->lists[i + addedCount] = array()->lists[i];for (unsigned i = 0; i < addedCount; i++)newArray->lists[i] = addedLists[i];free(array());setArray(newArray);validate();}else if (!list && addedCount == 1) {// 0 lists -> 1 listlist = addedLists[0];validate();}else {// 1 list -> many listsPtr<List> oldList = list;uint32_t oldCount = oldList ? 1 : 0;uint32_t newCount = oldCount + addedCount;setArray((array_t *)malloc(array_t::byteSize(newCount)));array()->count = newCount;if (oldList) array()->lists[addedCount] = oldList;for (unsigned i = 0; i < addedCount; i++)array()->lists[i] = addedLists[i];validate();}}
分类、类搭配加载
主类、分类都实现load方法
流程为_read_images 非懒加载类 -> realizeClassWithoutSwift ->methodizeClass -> attachToClass -> load_categories_nolock -> attachCategories
- realizeClassWithoutSwift,加载主类
- load_categories_nolock,加载分类
分类实现load方法,主类没有
- 分类有load,主类没有,主类被迫营业,实现非懒加载,通过data数据获取(llvm实现)
- 流程为_read_images 非懒加载类 -> realizeClassWithoutSwift -> methodizeClass -> attachToClass - 没有走attachCategories

断点realizeClassWithoutSwift,进行LLDB调试,观察是否有分类方法,发现分类方法并未加载到ro中
(lldb) p ro(const class_ro_t *) $0 = 0x00000001000044b0(lldb) p *$0(const class_ro_t) $1 = {flags = 0instanceStart = 8instanceSize = 40reserved = 0= {ivarLayout = 0x0000000000000000nonMetaclass = nil}name = {std::__1::atomic<const char *> = "LGPerson" {Value = 0x0000000100003c3a "LGPerson"}}baseMethodList = 0x00000001000044f8baseProtocols = nilivars = 0x00000001000045f0weakIvarLayout = 0x0000000000000000baseProperties = 0x0000000100004698_swiftMetadataInitializer_NEVER_USE = {}}(lldb) p $1.baseMethod()error: <user expression 2>:1:4: no member named 'baseMethod' in 'class_ro_t'$1.baseMethod()~~ ^(lldb) p $1.baseMethods()(method_list_t *) $2 = 0x00000001000044f8(lldb) p *$2(method_list_t) $3 = {entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 10)}(lldb) p $3.get(0).big(method_t::big) $4 = {name = "saySomething"types = 0x0000000100003de8 "v16@0:8"imp = 0x0000000100003a00 (KCObjcBuild`-[LGPerson saySomething])}Fix-it applied, fixed expression was:$3.get(0).big()(lldb) p $3.get(1).big(method_t::big) $5 = {name = "sayHello1"types = 0x0000000100003de8 "v16@0:8"imp = 0x0000000100003a30 (KCObjcBuild`-[LGPerson sayHello1])}Fix-it applied, fixed expression was:$3.get(1).big()(lldb) p $3.get(2).big(method_t::big) $6 = {name = "name"types = 0x0000000100003dfc "@16@0:8"imp = 0x0000000100003a60 (KCObjcBuild`-[LGPerson name])}Fix-it applied, fixed expression was:$3.get(2).big()(lldb) p $3.get(3).big(method_t::big) $7 = {name = "setName:"types = 0x0000000100003e04 "v24@0:8@16"imp = 0x0000000100003a90 (KCObjcBuild`-[LGPerson setName:])}Fix-it applied, fixed expression was:$3.get(3).big()(lldb) p $3.get(4).big(method_t::big) $8 = {name = "age"types = 0x0000000100003e13 "i16@0:8"imp = 0x0000000100003ac0 (KCObjcBuild`-[LGPerson age])}Fix-it applied, fixed expression was:$3.get(4).big()(lldb) p $3.get(5).big(method_t::big) $9 = {name = "setAge:"types = 0x0000000100003e1b "v20@0:8i16"imp = 0x0000000100003ae0 (KCObjcBuild`-[LGPerson setAge:])}Fix-it applied, fixed expression was:$3.get(5).big()(lldb) p $3.get(6).big(method_t::big) $10 = {name = "height"types = 0x0000000100003e26 "c16@0:8"imp = 0x0000000100003b00 (KCObjcBuild`-[LGPerson height])}Fix-it applied, fixed expression was:$3.get(6).big()(lldb) p $3.get(7).big(method_t::big) $11 = {name = "setHeight:"types = 0x0000000100003e2e "v20@0:8c16"imp = 0x0000000100003b20 (KCObjcBuild`-[LGPerson setHeight:])}Fix-it applied, fixed expression was:$3.get(7).big()(lldb) p $3.get(8).big(method_t::big) $12 = {name = "nickName"types = 0x0000000100003dfc "@16@0:8"imp = 0x0000000100003b40 (KCObjcBuild`-[LGPerson nickName])}Fix-it applied, fixed expression was:$3.get(8).big()(lldb) p $3.get(9).big(method_t::big) $13 = {name = "setNickName:"types = 0x0000000100003e04 "v24@0:8@16"imp = 0x0000000100003b70 (KCObjcBuild`-[LGPerson setNickName:])}Fix-it applied, fixed expression was:$3.get(9).big()
接着继续流程,在load_categories_nolock中断点,LLDB调试,发现此时出现了分类,其name是LGA,cls是LGPerson
(lldb) p count(size_t) $14 = 1(lldb) p cat(category_t *) $15 = 0x00000001000043d8(lldb) p *$15(category_t) $16 = {name = 0x0000000100003c36 "LGA"cls = 0x00000001000047f8instanceMethods = {ptr = 0x0000000100004310}classMethods = {ptr = 0x0000000100004360}protocols = nilinstanceProperties = 0x00000001000043b0_classProperties = nil}(lldb) p cls(Class) $17 = LGPerson
接着继续流程,load_categories_nolock会调用attachCategories,取出methodList,调用rwe->methods.attachLists,attachlist如上分析,二维指针
主类实现load方法,分类没有
主类实现load,分类同样会实现load,同样通过data数据获取
- 流程为_read_images 非懒加载类 -> realizeClassWithoutSwift -> methodizeClass -> attachToClass - 没有走attachCategories
主类、分类均未实现load方法
推迟到第一次消息发送时才初始化,加载已经实现在data中(llvm帮我们实现好,存在mach-o中)
多个分类情况
如果主类没有实现load,只有一个分类实现了load方法,啥也不会走,如果有两个分类实现了,会走prepare_load_methods,然后调起realizeClassWithoutSwift使主类被迫营业,这种情况就跟我们第一种情况一样,会走attachCategories方法了。。另外,如果新建了一个分类没有做任何操作,是不计入总的分类数量的,在cats_count中只计入已经有实现方法的分类(即使没有load方法也可以)。
补充
分类加载是否需要排序
- ※LGA数组指针(指针已排序prepareList排序)->LGB数组指针->主类数组指针,二分查找时,进行—操作,最外层也是array_t
- ※如果主类、分类都实现了load方法,则通过传入的cls加载,LGA、LGB、主类分开加载,是二维指针,不需要排序
- ※而其它情况都被实现在ro->data数据端中,二分查找取出时需要排序处理,找到最前面的,是一维指针
- 分类加载顺序取决于编译顺序,可以在Build Phases -> Compile Sources中修改
methodList 数据结构
- 本质是array_t数据结构entsize_list_tt,存储的是method指针,而不是method数据结构
- entsize_list_tt结构体中,取值是通过get方法,计算地址拿到指针
struct entsize_list_tt {...Element& getOrEnd(uint32_t i) const {ASSERT(i <= count);return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));}...}

主类没有load、分类实现了,数据加载问题
- 超过一个分类实现了load,主类被迫营业

- class_ro_t:指针地址,获取数据格式

