类中的成员变量
从前面的分析中我们知道,我们在rw
中读取到了方法列表、属性列表、协议列表,那成员变量呢?它在什么位置?
在class_rw_t
结构中有:
// objc-runtime-new.h
// 获取ro
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
我们可以关注一下class_ro_t
这个结构:
// objc-runtime-new.h
// ro结构
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name; // 类名
WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods; // 方法列表
protocol_list_t * baseProtocols; // 协议列表
const ivar_list_t * ivars; // 成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // 属性列表
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
const char *getName() const {
return name.load(std::memory_order_acquire);
}
class_ro_t *duplicate() const {
bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;
size_t size = sizeof(*this);
if (hasSwiftInitializer)
size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
if (hasSwiftInitializer)
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
#if __has_feature(ptrauth_calls)
// Re-sign the method list pointer.
ro->baseMethods = baseMethods;
#endif
return ro;
}
Class getNonMetaclass() const {
ASSERT(flags & RO_META);
return nonMetaclass;
}
const uint8_t *getIvarLayout() const {
if (flags & RO_META)
return nullptr;
return ivarLayout;
}
};
从class_ro_t
这个结构中,我们可以看到,它既有方法列表、属性列表、协议列表,还有成员变量列表ivars
,那我们就尝试读取一下:
读取出来之后,我们可以看到,它和方法列表是一样的,使用了entsize_list_tt
这个模板类,并且里面的count = 3
,应该就是我们的三个成员变量:_hobby
、_name
、_age
。我们也可以看一下ivar_list_t
和ivar_t
。
// objc-runtime-new.h
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};
// objc-runtime-new.h
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms. // *offset最初在一些x86_64平台上是64位的。
// We read and write only 32 bits of it. //我们只读写其中的32位
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values. //有些元数据提供全部64位。这对于unsigned little-endian值是无害的。
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit. //有些代码使用全部64位。class_addIvar()为它们的利益超额分配偏移量。
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead //对齐有时是-1;使用alignment()
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
那我们就可以读取一下ivar_list_t
中的三个值:
我们读取出来发现,这就是我们已知的三个成员变量。
rw ro rwe
那为什么在rw
中有methods
等信息,在ro
也要有呢?
我们可以先从 WWDC2020
开始介绍:
在WWDC2020
中,苹果工程师介绍了clean memory
和dirty memory
,也就是 “干净内存” 和 “脏内存” 。
clean memory
就是加载后不会改变的内存,class_ro_t
就是clean memory
,ro
就是read-only
(只读)。
dirty memory
就是进程运行时会发生变化的内存,class_rw_t
就是dirty memory
,rw
就是read-write
(读写)。
dirty memory
比clean memory
昂贵的多,只要进程在运行,就必须存在。但是clean memory
是可以移除的,节省内存,需要的时候,系统会在磁盘里读取出来。
dirty memory
可以分离出那些永远不会变的数据,可以把大部分的类数据存储为clean memory
。当一个类首次被使用,运行时会为它分配额外的存储容量,这个运行时分配的存储容量是 class_rw_t
,用于读取-编写数据。在这个数据结构中,我们存储了只有在运行时才会生成的新信息。
但为什么方法和属性也在只读数据中时,我们这里还要有方法和属性呢?
因为这些东西在运行时是可以被更改的。例如category
被加载时,它可以向类中添加新的方法,而且程序员可以使用运行时 API
动态地添加它们。因为 class_ro_t
是只读的,所以我们需要在 class_rw_t
中追踪这些东西。
但是这样做会占用很大的内存,毕竟系统中会有两份内容一样的东西。
经过检查实际设备上的使用情况,大约只有 10%
的类真正地更改了它们的方法。而且只有 Swift
类会使用这个 demangled name
字段,并且 Swift
类并不需要这一字段,除非有东西询问它们的 Object-C
名称时才需要。
所以,可以拆掉那些平时不用的部分,这将 class_rw_t
的大小减少了一半。拆出来的部分叫做 class_rw_ext_t
:
class_rw_ext_t
在源码中的结构:
// objc-runtime-new.h
// rwe的结构域
struct class_rw_ext_t {
DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods; //方法列表
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
对于那些需要改变的类,我们扩展出class_rw_ext_t
,供其使用。
这样大约90%
的类是不需要的,这样就节省了内存。
并且我们在class_rw_t
的结构代码中也可以看到这些变化:
// 方法列表
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) { // 有rwe时使用rwe
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {// 没有就是ro
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
}
}
在获取methods
时,判断了如果有rwe
就读取rwe
,没有就是ro
。
当然对于这些结构的变化,我们不需要去关心,只是用官方 API
就可以获取这些内容。
至此,我们探究了class_data_bits_t
。