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 lists
uint32_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 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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 = 0
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003c3a "LGPerson"
}
}
baseMethodList = 0x00000001000044f8
baseProtocols = nil
ivars = 0x00000001000045f0
weakIvarLayout = 0x0000000000000000
baseProperties = 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 = 0x00000001000047f8
instanceMethods = {
ptr = 0x0000000100004310
}
classMethods = {
ptr = 0x0000000100004360
}
protocols = nil
instanceProperties = 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:指针地址,获取数据格式