OC底层原理-cache和bits探索
类的属性存储
之前已经描述了类的存储,类的内存中存储了isa、superclass、cache、bits,前面已经了解了isa和superclass,那cache和bits分别是什么东西呢?接下来,分别对cache、bits进行探索。
cache
cache_t
用来快速查找执行函数的一种机制。
我们知道 Objective-C 为了实现其动态性,将函数地址的调用,转变成了通过SEL查找IMP的过程,这带来的后果是降低了方法调用的效率,为了解决这一问题。Objective-C 采用了方法缓存机制来提高效率。
lldb查看cache中的内容
方法:
main 函数定义一个HQPerson对象,并分两个步骤分别调用 init、 instanceMethod方法。通过lldb来观察cache中内容的变化。
//main.m
int main(int argc, const char * argv[]) {
HQPerson* person = [HQPerson alloc];
[person init];
[person instanceMethod];
return 0;
}
- 断点1,断在
init
方法调用之前。查看调用init
方法之前cache
的内容。
(lldb) x/4g person
0x101105170: 0x011d800100008305 0x0000000000000000
0x101105180: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008305 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008300
//偏移16字节,获取cache_t的指针地址,在objc_class结构体中,isa占8字节,superclass占8字节
(lldb) p (cache_t*)($1 + 0x10)
(cache_t *) $2 = 0x0000000100008310
(lldb) p *$2
(cache_t) $3 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298437520
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32820
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000803400000000
}
}
}
}
//获取cache中的buckets地址
(lldb) p $3.buckets()
(bucket_t *) $4 = 0x000000010034f390
//获取当前bucket的SEL
(lldb) p (*($4+0)).sel()
(SEL) $5 = <no value available>
//获取当前bucket中的IMP
(lldb) p (*($4+0)).imp($4, person.class)
(IMP) $6 = 0x0000000000000000
- 断点2,断在
instanceMethod
方法调用之前。查看调用init
方法之后cache
的内容。
(lldb) p *$2
(cache_t) $7 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4302567520
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 7
}
}
_flags = 32820
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001803400000007
}
}
}
}
(lldb) p $7.buckets()
(bucket_t *) $8 = 0x000000010073f860
(lldb) p (*($8+0)).sel()
(SEL) $9 = <no value available>
(lldb) p (*($8+0)).imp($8, person.class)
(IMP) $10 = 0x000000010033d0c0 (libobjc.A.dylib`-[NSObject respondsToSelector:] at NSObject.mm:2296)
(lldb) p (*($8+1)).sel()
(SEL) $11 = <no value available>
(lldb) p (*($8+1)).imp($8, person.class)
(IMP) $12 = 0x0000000000000000
(lldb) p (*($8+2)).sel()
(SEL) $13 = <no value available>
(lldb) p (*($8+2)).imp($8, person.class)
(IMP) $14 = 0x0000000000000000
(lldb) p (*($8+3)).sel()
(SEL) $15 = "init"
(lldb) p (*($8+3)).imp($8, person.class)
(IMP) $16 = 0x000000010033dd60 (libobjc.A.dylib`-[NSObject init] at NSObject.mm:2557)
以上可以看到,当发送了init
的消息之后,cache的buckets
中缓存了init
的imp和sel
。
- 断点3,断在 return 之前。查看调用
instanceMethod
方法之后cache
的内容。
(lldb) p *$2
(cache_t) $17 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4302567520
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 7
}
}
_flags = 32820
_occupied = 4
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0004803400000007
}
}
}
}
(lldb) p $17.buckets()
(bucket_t *) $18 = 0x000000010073f860
(lldb) p (*($18+0)).sel()
(SEL) $19 = "respondsToSelector:"
(lldb) p (*($18+0)).imp($18, person.class)
(IMP) $20 = 0x000000010033d0c0 (libobjc.A.dylib`-[NSObject respondsToSelector:] at NSObject.mm:2296)
(lldb) p (*($18+1)).sel()
(SEL) $21 = "instanceMethod"
(lldb) p (*($18+1)).imp($18, person.class)
(IMP) $22 = 0x0000000100003b10 (HQObjc`-[HQPerson instanceMethod])
(lldb) p (*($18+2)).sel()
(SEL) $23 = <no value available>
(lldb) p (*($18+2)).imp($18, person.class)
(IMP) $24 = 0x0000000000000000
(lldb) p (*($18+3)).sel()
(SEL) $25 = "init"
(lldb) p (*($18+3)).imp($18, person.class)
(IMP) $26 = 0x000000010033dd60 (libobjc.A.dylib`-[NSObject init] at NSObject.mm:2557)
以上可以看到,当发送了instanceMethod
的消息之后,cache的buckets
中缓存了instanceMethod
的imp和sel
。
以上通过lldb调试得知cache
是用来缓存消息的SEL和IMP
。但是如何进行缓存的还得进一步探索。
可在后续消息查找中,进行描述。
探索如何将消息缓存至cache中
当向对象发送消息时,如果未在cache
中找到该消息的IMP,则会调用cache::insert
接口,将消息的SEL和IMP
插入至cache
中。
接下来重要分析cache::insert
接口,即如何将消息的SEL和IMP插入至 cache 中。
void cache_t::insert(SEL sel, IMP imp, id receiver){
runtimeLock.assertLocked();
// Never cache before +initialize is done
if (slowpath(!cls()->isInitialized())) {
return;
}
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
cls()->nameForLogging());
}
#if DEBUG_TASK_THREADS
return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
ASSERT(sel != 0 && cls()->isInitialized());
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
该接口主要完成三个功能
-
获取当前缓存量
-
根据当前缓存量判断当前缓存内存的情况
- 当缓存为0时,开辟一段4个bucket大小的内存,缓存量为4
- 当缓存量大于0小于缓存填充率(75%)时,不对缓存内存操作
- 当缓存量超过缓存填充率(75%)时,将对当前的缓存进行2倍扩容,扩容后是新的缓存内存地址,旧的缓存会通过垃圾回收机制清理掉。
注意:当开辟新的缓存时,不会将旧缓存中的数据拷贝过来,因此,在旧缓存中保存的bucket会被清除。
-
通过
cache_hash
方法计算存储bucket
的索引,判断当前索引中是否已经有值。- 若当前索引有值且不为当前的
SEL
,则说明hash冲突,则通过cache_next
计算下一个索引。 - 若当前索引无值,则直接向当前索引的数组中插入
bucket
。
- 若当前索引有值且不为当前的
探索cache_t中的变量
_bucketsAndMaybeMask
:通过cache::insert
方法将SEL和IMP
存储在buckets数组
中,而该变量是指向buckets数组
的指针。
_maybeMask
:掩码数据,当需要将当前SEL和IMP存储在缓存内存时,需要通过该掩码数据计算hash索引,以及当hash冲突时,需要使用掩码数据计算下一个hash索引。
//计算hash索引
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
//当发生hash冲突时,计算下一个hash索引
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
_flags
:存储一些标志值,具体如下:
// class or superclass has .cxx_construct/.cxx_destruct implementation
// FAST_CACHE_HAS_CXX_DTOR is the first bit so that setting it in
// isa_t::has_cxx_dtor is a single bfi
#define FAST_CACHE_HAS_CXX_DTOR (1<<0)
#define FAST_CACHE_HAS_CXX_CTOR (1<<1)
// Denormalized RO_META to avoid an indirection
#define FAST_CACHE_META (1<<2)
// Fast Alloc fields:
// This stores the word-aligned size of instances + "ALLOC_DELTA16",
// or 0 if the instance size doesn't fit.
//
// These bits occupy the same bits than in the instance size, so that
// the size can be extracted with a simple mask operation.
//
// FAST_CACHE_ALLOC_MASK16 allows to extract the instance size rounded
// rounded up to the next 16 byte boundary, which is a fastpath for
// _objc_rootAllocWithZone()
#define FAST_CACHE_ALLOC_MASK 0x1ff8
#define FAST_CACHE_ALLOC_MASK16 0x1ff0
#define FAST_CACHE_ALLOC_DELTA16 0x0008
// class's instances requires raw isa
#define FAST_CACHE_REQUIRES_RAW_ISA (1<<13)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
// class or superclass has default new/self/class/respondsToSelector/isKindOfClass
#define FAST_CACHE_HAS_DEFAULT_CORE (1<<15)
_occupied
:当前缓存中存储的bucket
的个数。
- 当调用实例方法时,
_occupied
加1; - 当调用init方法时,
_occupied
加1; - 当获取属性值或者设置属性值时,
_occupied
加1;(获取属性值或者设置属性值是向对象发送getter/setter
消息)
bits
bits
是class_data_bits_t
类型的结构体,其中只有一个成员变量uintptr_t类型的bits
。在编译阶段该指针会指向一个class_ro_t
结构体,在运行时会封装成class_rw_t
结构体。
那bits
到底是存储了什么呢?接下来我们一步一步分析。
class_ro_t
class_ro_t
存储了很多在编译时期就确定的类的信息。其内部包含了类名、ivar、方法、属性等。是只读类型的结构。
struct class_ro_t {
uint32_t flags; //配合mask 可以用来判断,元类,根类等
uint32_t instanceStart; //non-fragile判断依据
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
接下来我们尝试读取一下class_ro_t
的信息。为了验证class_ro_t
是在编译时就已经生成的,我们将断点断在_objc_init函数中。由于objc_init
是runtime
入口函数(runtime还没有处理类的元数据)。
- 根据类在编译时就已经确定其地址,可得到HQPerson类的地址为:0x0000000100008380
- 由HQPerson类的首地址,偏移32字节,即可得到
class_data_bits_t
的地址。
(lldb) p/x 0x0000000100008380+0x20
(long) $2 = 0x00000001000083a0
(lldb) p (class_data_bits_t*)$2
(class_data_bits_t *) $3 = 0x00000001000083a0
-
class_data_bits_t
结构体,提供data
方法获取class_rw_t
,由于此时还未对类的元数据进行处理,读取class_rw_t
时,会报读取失败的错误,说明class_rw_t
是运行时才有内容。将class_rw_t
结构体强转为class_ro_t
结构体,再读取其中内容时,发现是可以读取的,因此可以验证class_ro_t
结构体是在编译时就生成的。
(lldb) p $3->data()
(class_rw_t *) $4 = 0x00000001000080f8
//由于此时还未处理类的元数据,因此无法获取class_rw_t结构体中内容
(lldb) p $4.methods()
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x48).
The process has been returned to the state before expression evaluation.
//将class_rw_t结构体强转成class_ro_t
(lldb) p (class_ro_t*)$4
(class_ro_t *) $5 = 0x00000001000080f8
//读取class_ro_t的内容
(lldb) p *$5
(class_ro_t) $6 = {
flags = 388
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000100003f77 "\x04"
nonMetaclass = 0x0000000100003f77
}
name = {
std::__1::atomic<const char *> = "HQPerson" {
Value = 0x0000000100003f6e "HQPerson"
}
}
//类的实例方法存储地址
baseMethodList = 0x0000000100008140
//类的协议存储地址
baseProtocols = 0x0000000000000000
//成员变量存储地址
ivars = 0x0000000100008238
weakIvarLayout = 0x0000000000000000
//类的属性存储地址
baseProperties = 0x00000001000082c0
_swiftMetadataInitializer_NEVER_USE = {}
}
- 既然
class_ro_t
结构体在编译时已经确定类的信息,那使用lldb来将这些类的信息打印出来看看
//验证存储实例方法的地址中是否存储了实例方法
(lldb) p $6.baseMethodList
(void *) $7 = 0x0000000100008140
(lldb) p (method_list_t*)$7
(method_list_t *) $8 = 0x0000000100008140
(lldb) p ($8->get(0)).big()
(method_t::big) $9 = {
name = "instanceMethod"
types = 0x0000000100003f79 "v16@0:8"
imp = 0x00000001000039a0 (HQObjc`-[HQPerson instanceMethod])
}
(lldb) p ($8->get(1)).big()
(method_t::big) $10 = {
name = "instanceMethod1"
types = 0x0000000100003f79 "v16@0:8"
imp = 0x00000001000039d0 (HQObjc`-[HQPerson instanceMethod1])
}
//验证存储属性的地址中是否存储了属性
(lldb) p $6.baseProperties
(property_list_t *) $12 = 0x00000001000082c0
(lldb) p $12->get(0)
(property_t) $13 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $12->get(1)
(property_t) $14 = (name = "propertyName", attributes = "T@\"NSString\",C,N,V_propertyName")
//验证存储成员变量的地址中是否存储了成员变量
(lldb) p $6.ivars
(const ivar_list_t *) $15 = 0x0000000100008238
(lldb) p $15->get(0)
(ivar_t) $16 = {
offset = 0x0000000100008338
name = 0x0000000100003d51 "hobby"
type = 0x0000000100003f81 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $15->get(1)
(ivar_t) $17 = {
offset = 0x0000000100008340
name = 0x0000000100003d57 "memberHobby"
type = 0x0000000100003f81 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $15->get(2)
(ivar_t) $18 = {
offset = 0x0000000100008348
name = 0x0000000100003d63 "_name"
type = 0x0000000100003f81 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $15->get(3)
(ivar_t) $19 = {
offset = 0x0000000100008350
name = 0x0000000100003d69 "_propertyName"
type = 0x0000000100003f81 "@\"NSString\""
alignment_raw = 3
size = 8
}
总结
-
class_ro_t
结构体是用来存储类的相关信息,如类名、变量、属性、实例方法等; -
class_ro_t
结构体在编译时期已经存储了类的信息。 -
class_ro_t
结构体的内容是只读
的。
class_rw_t
从上面的章节看出class_ro_t
存储的大多是类在编译时就已经确定的信息,但是Objective-C
又是一门动态语言,因此需要另一个可以运行时读写
数据,也就是class_rw_t
。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
}
接下来我们尝试读取一下class_rw_t
的信息。由于class_rw_t
是在运行时生成的,因此,需要将objc_init
的断点过掉。
- 将断点断在
[HQPerson alloc]
之后。通过类的首地址,偏移32字节得到class_data_bits_t
的地址。
(lldb) p/x 0x0000000100008380+0x20
(long) $2 = 0x00000001000083a0
(lldb) p (class_data_bits_t*)$2
(class_data_bits_t *) $3 = 0x00000001000083a0
- 通过
class_data_bits_t
结构体中的data
方法,获取class_rw_t
结构体指针
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101998be0
- 读取
class_rw_t
结构体中的实例方法(methods)
、属性(properties)
,并与class_ro_t
结构中的baseMethodList
、ivars
、baseProperties
进行比较。注意:由于class_rw_t
是在运行时生成的,而成员变量是无法在运行进行添加的,因此在class_rw_t
结构体中没有成员变量。
//获取class_rw_t中的实例方法(methods)
(lldb) p $3->methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008140 //与class_ro_t中的baseMethodList = 0x0000000100008140相同
}
arrayAndFlag = 4295000384
}
}
}
(lldb) p (method_list_t*)0x0000000100008140
(method_list_t *) $5 = 0x0000000100008140
(lldb) p ($5->get(0).big())
(method_t::big) $6 = {
name = "instanceMethod"
types = 0x0000000100003f79 "v16@0:8"
imp = 0x00000001000039a0 (HQObjc`-[HQPerson instanceMethod])
}
(lldb) p ($5->get(1).big())
(method_t::big) $7 = {
name = "instanceMethod1"
types = 0x0000000100003f79 "v16@0:8"
imp = 0x00000001000039d0 (HQObjc`-[HQPerson instanceMethod1])
}
//获取class_rw_t中的属性(properties)
(lldb) p $3->properties()
(const property_array_t) $8 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000082c0 //与class_ro_t中的baseProperties = 0x00000001000082c0相同
}
arrayAndFlag = 4295000768
}
}
}
(lldb) p (property_list_t*)0x00000001000082c0
(property_list_t *) $9 = 0x00000001000082c0
(lldb) p $9->get(0)
(property_t) $10 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $9->get(1)
(property_t) $11 = (name = "propertyName", attributes = "T@\"NSString\",C,N,V_propertyName")
动态的增加方法后查看rw、ro的变化情况
在HQPerson中,通过动态方法解析
的函数来为当前类增加一个方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了", NSStringFromSelector(sel));
if (sel == @selector(say666)) {
//获取instanceMethods方法的imp
IMP imp = class_getMethodImplementation(self, @selector(instanceMethods));
//获取instanceMethods的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(instanceMethods));
//获取instanceMethods的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
class_addMethod(self, sel, imp, type);
return NO;
}
return NO;
}
- 【步骤1】在
class_addMethod
方法放置断点,查看当前未添加方法时rw、ro的情况。
(lldb) p/x person.class
(Class) $0 = 0x0000000100008248 HQPerson
(lldb) p/x 0x0000000100008248+0x20
(long) $1 = 0x0000000100008268
(lldb) p (class_data_bits_t*)$1
(class_data_bits_t *) $2 = 0x0000000100008268
(lldb) p $2->safe_ro()
(const class_ro_t *) $3 = 0x00000001000080e8
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
= {
ivarLayout = 0x0000000100003f63 "\x02"
nonMetaclass = 0x0000000100003f63
}
name = {
std::__1::atomic<const char *> = "HQPerson" {
Value = 0x0000000100003f5a "HQPerson"
}
}
baseMethodList = 0x0000000100008130
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008198
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000081e0
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $2->data()
(class_rw_t *) $5 = 0x0000000101b04190
(lldb) p $5->methods()
(const method_array_t) $6 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008130
}
arrayAndFlag = 4295000368
}
}
}
(lldb) p $6.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $7 = 0x00000001005db7c0
(lldb) x/4g 0x00000001005db7c0
0x1005db7c0: 0x0000000100008130 0x0000000000000000
0x1005db7d0: 0x0000000000000000 0x0000000000000000
此时可以看出,ro
中baseMethodList = 0x0000000100008130
与rw
中ptr = 0x0000000100008130
是一致的。
在rw
的结构体中,暂时只存储了一个方法列表的地址
,即baseMethodList
。
- 跳过断点,向HQPerson类中添加
say666
的方法,此时再来观察一下ro、rw的情况
(lldb) p $2->safe_ro()
(const class_ro_t *) $3 = 0x00000001000080e8
(lldb) p *$3
(const class_ro_t) $4 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
= {
ivarLayout = 0x0000000100003f63 "\x02"
nonMetaclass = 0x0000000100003f63
}
name = {
std::__1::atomic<const char *> = "HQPerson" {
Value = 0x0000000100003f5a "HQPerson"
}
}
baseMethodList = 0x0000000100008130
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008198
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000081e0
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $2->data()
(class_rw_t *) $14 = 0x0000000101b04190
(lldb) p $14->methods()
(const method_array_t) $15 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000101a562e1
}
arrayAndFlag = 4322583265
}
}
}
(lldb) p $15.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $16 = 0x0000000101a562e8
(lldb) x/4g 0x0000000101a562e8
0x101a562e8: 0x0000000101a56290 0x0000000100008130
0x101a562f8: 0x0000000000000000 0x0000000000000000
(lldb) p (method_list_t*)0x0000000101a56290
(method_list_t *) $24 = 0x0000000101a56290
(lldb) p $24->get(0).big()
(method_t::big) $27 = {
name = "say666"
types = 0x0000000100003f65 "v16@0:8"
imp = 0x0000000100003cb0 (HQObjc`-[HQPerson instanceMethods] at HQPerson.m:13)
}
对比未添加方法之前,ro
不发生变化。
添加方法之后,rw
的中保存着两个方法列表
的地址。其中一个地址是baseMethodList
,另一个地址是存储了动态添加方法的方法列表
地址。
总结
class_rw_t
结构体是在运行时用来存储类的相关信息,如类名、属性、实例方法等。
class_rw_t
结构体中没有成员变量。
class_rw_t
结构体的内容是可读写
的。在动态添加方法
后class_rw_t
结构体会动态的添加方法列表
,用于存储动态添加的方法
。