在OC底层探索06-isa本身藏了多少信息你知道吗?分析了isa
。
在平时的开发中应该都接触或者使用过
缓存
的技术,目的就是提高执行效率,用空间换取时间
。当然apple在这方面一定也有其特别的地方。
// 再熟悉一下objc_class的结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
...
}
本文中会注重介绍objc_object
中的cache
提到缓存那么cache
面缓存的是什么呢:属性还方法?其实很好猜测,平时开发中使用最多的就是方法,因为只有方法才会引起变化
。下面会通过两种方式进行验证这个猜测。
首先了解一下这3个宏定义
-
define CACHE_MASK_STORAGE_OUTLINED //代表当前环境:模拟器、macos
-
define CACHE_MASK_STORAGE_HIGH_16 2 //代表当前环境:64位真机
-
define CACHE_MASK_STORAGE_LOW_4 3 //代表当前环境:低于64位真机
通过cache_t了解cache到底缓存了什么?
struct cache_t {
//不同设备环境数据结构不同
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
uint16_t _flags;
uint16_t _occupied;
...
}
struct bucket_t {
#if __arm64__
explicit_atomic<uintptr_t> _imp; //方法指针
explicit_atomic<SEL> _sel; //方法标示
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
}
-
cache根据当前环境分为3个版本.
-
explicit_atomic<X>
:类似于swift
中的泛型。将当前类型设置为原子性
,也就是将其设置为线程安全。(毕竟在多线程中调用方法的场景太多了) -
mask_t _mask_unused: 根据命名当前参数还未进行使用
-
主要信息存在:
_buckets | _maskAndBuckets
中
cache_t的结构图
下方是最新的objc-781
的 objc_class结构
struct objc_object { //所有对象的模板类
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
// Class ISA; //偏移量:8位
Class superclass; //偏移量:8位
cache_t cache; //偏移量:8 + 4 + 2 + 2 = 16位
class_data_bits_t bits;
class_rw_t *data() const {
return bits.data();
}
...
}
内存偏移
这lldb中我们无法直接访问objc_class
中的信息,只能通过指针访问的方式来进行验证,所以这里需要用到内存偏移
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - -- %p --- %p", &c, &c[0], &c[1],&c[2]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
企业微信截图_7568a4b2-695a-48d0-97ee-303210e61466.png
- int是4位我们可以直接对
地址进行+4来访问数组的下一个成员。
类的内部信息
手动计算objc_class
源码中class_data_bits_t bits;
的偏移量:32位
,(在加上结构体:class_data_bits_t的8位,正好40位。印证了上文中的猜测)
1.拿到objc_class中的class_rw_t
(lldb) p/x HRTest.class
(Class) $0 = 0x00000001000033c0 HRTest //类头地址
(lldb) p (class_data_bits_t *)0x00000001000033d0 //类地址偏移0x20
(class_data_bits_t *) $1 = 0x00000001000033d0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001000033e8 //数据都存放在class_rw_t里
2.查看class_rw_t源码
注:此处只放出和目标有关的信息
const class_ro_t *ro()
const method_array_t methods() //对象方法列表
const property_array_t properties() //属性
const protocol_array_t protocols() //类的协议
类中方法列表
(class_rw_t *) $2 = 0x00000001012b46c0 //class_rw_t
(lldb) p $2->methods() //方法list
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002108
arrayAndFlag = 4294975752
}
}
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x0000000100002108
(lldb) p *$4
(method_list_t) $5 = {
...
}
(lldb) p $5.get(0)
(method_t) $6 = {
name = "say222" //对象方法say222
types = 0x0000000100000f73 "v16@0:8"
imp = 0x0000000100000d50 (HRTest`-[HRTest say222])
}
(lldb) p $5.get(1)
(method_t) $7 = {
name = ".cxx_destruct"
types = 0x0000000100000f73 "v16@0:8"
imp = 0x0000000100000d60 (HRTest`-[HRTest .cxx_destruct])
}
(lldb) p $5.get(2)
(method_t) $8 = {
name = "name" //自动生成属性的get方法
types = 0x0000000100000f87 "@16@0:8"
imp = 0x0000000100000da0 (HRTest`-[HRTest name])
}
(lldb) p $5.get(3)
(method_t) $9 = {
name = "setName:" //自动生成属性的set方法
types = 0x0000000100000f8f "v24@0:8@16"
imp = 0x0000000100000dd0 (HRTest`-[HRTest setName:])
}
(lldb) p $5.get(4)
Assertion failed: (i < count)... //数组越界了
找到了对象方法
,属性的get,set
方法,唯独没有知道类方法。
元类中方法列表
(lldb) x/gx HRTest.class
0x100002218: 0x00000001000021f0 //类的元类
(lldb) p (class_data_bits_t *)0x0000000100002210
(class_data_bits_t *) $1 = 0x0000000100002210
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001015338d0
(lldb) p $2->methods()
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020a0
arrayAndFlag = 4294975648
}
}
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x00000001000020a0
(lldb) p *$4
(method_list_t) $5 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "say666"
types = 0x0000000100000f73 "v16@0:8"
imp = 0x0000000100000d40 (HRTest`+[HRTest say666])
}
}
}
- 在元类中找到了
类方法say666
属性
查看方式与方法相同:properties()
成员变量
(class_rw_t *) $2 = 0x0000000101822660 //class_rw_t
(lldb) p $2->ro() //ro
(const class_ro_t *) $3 = 0x00000001000020c0
(lldb) p $3->ivars //成员变量
(const ivar_list_t *const) $4 = 0x0000000100002170
(lldb) p *$4 //取地址
(const ivar_list_t) $5 = {
...
}
(lldb) p $5.get(0)
(ivar_t) $7 = { //定义的成员变量
offset = 0x00000001000021e0
name = 0x0000000100000ed2 "HRTestName"
type = 0x0000000100000f7b "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $5.get(1)
(ivar_t) $8 = { //name属性自定生成的:_name成员变量
offset = 0x00000001000021e8
name = 0x0000000100000edd "_name"
type = 0x0000000100000f7b "@\"NSString\""
alignment_raw = 3
size = 8
}
- 成员变量是在
class_rw_t
->ro()
->ivars
中 - 成员变量不单有开发者创建的,还有属性也会自动创建一个
_属性名
iOS14之后类的结构发生变化
- 将需要在运行时改变的数据单独放在
class_rw_ext_t
这个结构中。超过90%的普通类都不会动态进行修改方法、协议、属性
,所以大多数只需要class_rw_t
这个结构就完整了。 - 90%的类省了四个字段: 4*8 = 32个字节
网友评论