概述
出于对Advancements in the Objective-C runtime中class_rw_t内存优化的好奇,有几个问题想了解:
- 问题一:
class_rw_t、class_rw_ext_t、class_ro_t三者之间的关系是如何实现的? - 问题二:什么条件下才会创建并使用
class_rw_ext_t? - 问题三:
class_rw_ext_t与class_ro_t中都有方法、属性、协议列表,数据有什么区别呢? - 问题四: 动态添加属性、方法、协议是怎么实现的?操作的哪些数据?
class_rw_t
下面是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;
private:
/* 别名操作,类似typedef */
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
/* 获取ro_or_rw_ext_t */
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
/* 将class_ro_t存储到成员变量ro_or_rw_ext */
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
/*
* 将class_ro_t赋值给class_rw_ext_t的成员ro,
* 并将class_rw_ext_t的指针存储到成员变量ro_or_rw_ext中
*/
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
/* 对成员变量`flags`的操作; */
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
/* 对成员变量`ro_or_rw_ext`的创建、存、取操作; */
/* 获取class_rw_ext_t */
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
/* 获取class_rw_ext_t,如果没有则创建一个 */
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
/*
* 先获取class_rw_ext_t成员变量,判断类型,
* 如果是class_rw_ext_t,要取里面的成员变量ro,
* 这里的判断加了一层slowpath,一个优化处理
*/
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*
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
/* 通过成员变量`ro_or_rw_ext`获取方法、属性、协议列表;*/
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
/*
* 通过class_ro_t创建class_rw_ext_t,
* 并将class_ro_t方法、属性、协议列表都添加到class_rw_ext_t中对应的列表内
* 最后调用set_ro_or_rwe,存储指针数据
*/
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
auto rwe = objc::zalloc<class_rw_ext_t>();
rwe->version = (ro->flags & RO_META) ? 7 : 0;
method_list_t *list = ro->baseMethods();
if (list) {
if (deepCopy) list = list->duplicate();
rwe->methods.attachLists(&list, 1);
}
// See comments in objc_duplicateClass
// property lists and protocol lists historically
// have not been deep-copied
//
// This is probably wrong and ought to be fixed some day
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
set_ro_or_rwe(rwe, ro);
return rwe;
}
explicit_atomic是继承自C++的atomic,里面封装了一个值的类型,它的访问保证不会导致数据的竞争,并且可以用于在不同的线程之间同步内存访问。详细可参考atomic 总结
PointerUnion, 个人理解是一个封装可同时兼容class_rw_ext_t、class_ro_t类型数的类,就是可以存储要么是class_rw_ext_t类型的数据,要么是class_ro_t类型的数据,不能同时存储,鱼和熊掌不可兼得,其中有一个is()方法,用来判断是某一个特性类型的数据,get()方法,获取特性类型的数据。
成员变量ro_or_rw_ext
结构体class_rw_t中有6个成员变量,这次探索的关键数据就是explicit_atomic<uintptr_t> ro_or_rw_ext;,是一个原子类型数据,存储着class_rw_ext_t或class_ro_t指针,为什么是或呢?
方法
里面的方法可以大概分为三类:
对成员变量flags的操作;
对成员变量flags的一些操作,包括数据更新、清除操作;
对成员变量ro_or_rw_ext的创建、存、取操作;
创建时会将class_ro_t的方法、属性、协议列表都添加到class_rw_ext_t对应的列表中,然后通过PointerUnion来存储class_rw_ext_t或class_ro_t结构体的指针,取数据也是通过PointerUnion来操作的。
通过成员变量ro_or_rw_ext获取方法、属性、协议列表;
在取方法、属性、协议列表数据时,先获取成员变量ro_or_rw_ext,判断其类型是class_rw_ext_t还是class_ro_t,然后在对应的列表中取出数据。
class_rw_ext_t
数据结构如下
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_ro_t的ro,没有额外的方法。
class_ro_t
class_ro_t,存储着类的原始数据,有名称、方法列表、协议列表、属性列表、成员变量列表等等。
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;
// 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;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
......
method_list_t *baseMethods() const {
......
}
探索问题一:关系
通过以上的描述,class_rw_t、class_rw_ext_t、class_ro_t三者应该有两种关系图:
不使用class_rw_ext_t
class_rw_t中成员变量ro_or_rw_ext直接指向class_ro_t,该关系链可以省去class_rw_ext_t结构体的数据,较少内存消耗。
使用class_rw_ext_t
class_rw_t中成员变量ro_or_rw_ext直接指向class_rw_ext_t,class_rw_ext_t的成员变量ro指向class_ro_t,该关系链就无法减少内存消耗了,不要小看这些内存,积少成多。
探索问题二:创建&使用条件
创建
创建class_rw_ext_t在外部一般使用的是extAllocIfNeeded()方法,下面是该放方法的调用链(里面有一些内部方法,未在runtime.h中暴露)
其中的
objc_class::demangledName说明一下,如果不是元类、Swift的类,直接通过ro()->getName(),否则通过extAllocIfNeeded()创建class_rw_ext_t结构体,在进行成员变量demangledName赋值操作,然后返回class_rw_ext_t结构体中的成员变量demangledName。所以Swift类、元类都会使用class_rw_ext_t?
使用条件
通过上面的调用链不难发现,有几处会触发创建class_rw_ext_t结构体,分别是:
- 动态添加属性
- 动态添加方法
- 动态添加协议
- 当前类存在类别
- 给类设置版本 想进一步减少内存的可通过以上条件去做处理了。
探索问题三:数据区别
在对比数据之前可以先了解一下entsize_list_tt&list_array_tt两个数据结构。
方法列表
class_rw_ext_t中存储方法列表使用的是method_array_t,这是一个继承自list_array_tt的数据,里面会存储method_list_t,而class_ro_t中存储方法列表使用的是method_list_t这是一个普通数组,在通过class_rw_t::extAlloc创建class_rw_ext_t结构体时,会使用attachLists方法进行方法列表添加,此时会使用method_array_t中的list指针指向method_list_t,如果在进行方法添加(包括动态添加、类别方法)则会启用二维数组的方式进行存储。
属性列表
class_rw_ext_t中存储属性列表使用的是property_array_t,class_ro_t中使用的是property_list_t,列表添加数据方式和方法列表一样。
协议列表
class_rw_ext_t中存储属性列表使用的是protocol_array_t,class_ro_t中使用的是protocol_list_t,列表添加数据方式和方法列表一样。
探索问题四:动态添加的实现
添加属性
动态添加属性最后调用的是
_class_addProperty方法,详细代码如下:
/***********************************************************************
* class_addProperty
* Adds a property to a class.
* Locking: acquires runtimeLock
**********************************************************************/
static bool
_class_addProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int count,
bool replace)
{
if (!cls) return NO;
if (!name) return NO;
// 先处理是否需要替换属性
property_t *prop = class_getProperty(cls, name);
if (prop && !replace) {
// already exists, refuse to replace
return NO;
}
else if (prop) {
// replace existing
mutex_locker_t lock(runtimeLock);
try_free(prop->attributes);
prop->attributes = copyPropertyAttributeString(attrs, count);
return YES;
}
else {
// 下面是添加属性的操作
mutex_locker_t lock(runtimeLock);
auto rwe = cls->data()->extAllocIfNeeded();
ASSERT(cls->isRealized());
property_list_t *proplist = (property_list_t *)
malloc(property_list_t::byteSize(sizeof(property_t), 1));
proplist->count = 1;
proplist->entsizeAndFlags = sizeof(property_t);
proplist->begin()->name = strdupIfMutable(name);
proplist->begin()->attributes = copyPropertyAttributeString(attrs, count);
rwe->properties.attachLists(&proplist, 1);
return YES;
}
}
BOOL
class_addProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int n)
{
return _class_addProperty(cls, name, attrs, n, NO);
}
void
class_replaceProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int n)
{
_class_addProperty(cls, name, attrs, n, YES);
}
_class_addProperty该方法可以同时支持替换、添加属性,添加属性时先通过extAllocIfNeeded()方法获取class_rw_ext_t结构体指针rwe,创建属性列表(property_list_t *)proplist,向属性列表中添加属性,最后在将proplist添加到rwe的成员变量properties(属性列表)中。下面是添加属性的流程图。
如何通过runtiem进行添加属性操作,可参考ios动态添加属性的几种方法
添加方法
添加方法模式其实和添加属性差不多,
addMethod()、addMethods()是支持方法替换和添加的。最后的方法列表添加是放在addMethods_finish()中的,其中会有方法排序(使用的是std::stable_sort,其中的sorter是method_t::SortBySELAddress)、清除方法缓存操作,处理好方法列表,再添加到class_rw_ext_t中的方法列表内。
疑问:prepareMethodLists()中,有清除缓存操作,有方法排序,最后的方法扫描是做什么的?
下面是实现代码:
static void
addMethods_finish(Class cls, method_list_t *newlist)
{
auto rwe = cls->data()->extAllocIfNeeded();
if (newlist->count > 1) {
method_t::SortBySELAddress sorter;
std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter);
}
prepareMethodLists(cls, &newlist, 1, NO, NO, __func__);
rwe->methods.attachLists(&newlist, 1);
// If the class being modified has a constant cache,
// then all children classes are flattened constant caches
// and need to be flushed as well.
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
ASSERT(types);
ASSERT(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp(false);
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
addMethods_finish(cls, newlist);
result = nil;
}
return result;
}
/**********************************************************************
* addMethods
* Add the given methods to a class in bulk.
* Returns the selectors which could not be added, when replace == NO and a
* method already exists. The returned selectors are NULL terminated and must be
* freed by the caller. They are NULL if no failures occurred.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static SEL *
addMethods(Class cls, const SEL *names, const IMP *imps, const char **types,
uint32_t count, bool replace, uint32_t *outFailedCount)
{
runtimeLock.assertLocked();
ASSERT(names);
ASSERT(imps);
ASSERT(types);
ASSERT(cls->isRealized());
method_list_t *newlist;
size_t newlistSize = method_list_t::byteSize(sizeof(struct method_t::big), count);
newlist = (method_list_t *)calloc(newlistSize, 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 0;
SEL *failedNames = nil;
uint32_t failedCount = 0;
for (uint32_t i = 0; i < count; i++) {
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, names[i]))) {
// already exists
if (!replace) {
// report failure
if (failedNames == nil) {
// allocate an extra entry for a trailing NULL in case
// every method fails
failedNames = (SEL *)calloc(sizeof(*failedNames),
count + 1);
}
failedNames[failedCount] = m->name();
failedCount++;
} else {
_method_setImplementation(cls, m, imps[i]);
}
} else {
auto &newmethod = newlist->end()->big();
newmethod.name = names[i];
newmethod.types = strdupIfMutable(types[i]);
newmethod.imp = imps[i];
newlist->count++;
}
}
if (newlist->count > 0) {
// fixme resize newlist because it may have been over-allocated above.
// Note that realloc() alone doesn't work due to ptrauth.
addMethods_finish(cls, newlist);
} else {
// Attaching the method list to the class consumes it. If we don't
// do that, we have to free the memory ourselves.
free(newlist);
}
if (outFailedCount) *outFailedCount = failedCount;
return failedNames;
}
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
SEL *
class_addMethodsBulk(Class cls, const SEL *names, const IMP *imps,
const char **types, uint32_t count,
uint32_t *outFailedCount)
{
if (!cls) {
if (outFailedCount) *outFailedCount = count;
return (SEL *)memdup(names, count * sizeof(*names));
}
mutex_locker_t lock(runtimeLock);
return addMethods(cls, names, imps, types, count, NO, outFailedCount);
}
void
class_replaceMethodsBulk(Class cls, const SEL *names, const IMP *imps,
const char **types, uint32_t count)
{
if (!cls) return;
mutex_locker_t lock(runtimeLock);
addMethods(cls, names, imps, types, count, YES, nil);
}
添加协议
和添加属性及其相似,先判类是否存在,在判断当前类是否已经遵循了要添加的协议,如果满足了添加条件,同样使用extAllocIfNeeded()方法获取class_rw_ext_t的结构体rwe,创建protolist,将要添加的协议添加到protolist中,最后在将protolist添加到rwe的protocols中,添加完成。
/***********************************************************************
* class_addProtocol
* Adds a protocol to a class.
* Locking: acquires runtimeLock
**********************************************************************/
BOOL class_addProtocol(Class cls, Protocol *protocol_gen)
{
protocol_t *protocol = newprotocol(protocol_gen);
if (!cls) return NO;
if (class_conformsToProtocol(cls, protocol_gen)) return NO;
mutex_locker_t lock(runtimeLock);
auto rwe = cls->data()->extAllocIfNeeded();
ASSERT(cls->isRealized());
// fixme optimize
protocol_list_t *protolist = (protocol_list_t *)
malloc(sizeof(protocol_list_t) + sizeof(protocol_t *));
protolist->count = 1;
protolist->list[0] = (protocol_ref_t)protocol;
rwe->protocols.attachLists(&protolist, 1);
// fixme metaclass?
return YES;
}
LAST
四个问题都应该有答案了,看底层代码在内存优化方面,能少用一个变量就少用,能共用就共用,例如PointerUnion与list_array_tt两个类的设计。