【objc4-NSObject】(三):class_rw_t

1,865 阅读12分钟

概述

出于对Advancements in the Objective-C runtimeclass_rw_t内存优化的好奇,有几个问题想了解:

  • 问题一:class_rw_tclass_rw_ext_tclass_ro_t三者之间的关系是如何实现的?
  • 问题二:什么条件下才会创建并使用class_rw_ext_t
  • 问题三: class_rw_ext_tclass_ro_t中都有方法、属性、协议列表,数据有什么区别呢?
  • 问题四: 动态添加属性、方法、协议是怎么实现的?操作的哪些数据? image.png

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_tclass_ro_t指针,为什么是呢?

方法

里面的方法可以大概分为三类:

对成员变量flags的操作;

对成员变量flags的一些操作,包括数据更新、清除操作;

对成员变量ro_or_rw_ext的创建、存、取操作;

创建时会将class_ro_t的方法、属性、协议列表都添加到class_rw_ext_t对应的列表中,然后通过PointerUnion来存储class_rw_ext_tclass_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_tclass_rw_ext_tclass_ro_t三者应该有两种关系图:

不使用class_rw_ext_t

image.png class_rw_t中成员变量ro_or_rw_ext直接指向class_ro_t,该关系链可以省去class_rw_ext_t结构体的数据,较少内存消耗。

使用class_rw_ext_t

image.png class_rw_t中成员变量ro_or_rw_ext直接指向class_rw_ext_tclass_rw_ext_t的成员变量ro指向class_ro_t,该关系链就无法减少内存消耗了,不要小看这些内存,积少成多。

探索问题二:创建&使用条件

创建

创建class_rw_ext_t在外部一般使用的是extAllocIfNeeded()方法,下面是该放方法的调用链(里面有一些内部方法,未在runtime.h中暴露) extAllocIfNeeded.png 其中的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_tclass_ro_t中使用的是property_list_t,列表添加数据方式和方法列表一样。

协议列表

class_rw_ext_t中存储属性列表使用的是protocol_array_tclass_ro_t中使用的是protocol_list_t,列表添加数据方式和方法列表一样。

探索问题四:动态添加的实现

添加属性

_class_addProperty.png 动态添加属性最后调用的是_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(属性列表)中。下面是添加属性的流程图。

添加属性.png

如何通过runtiem进行添加属性操作,可参考ios动态添加属性的几种方法

添加方法

addMethods_finish.png 添加方法模式其实和添加属性差不多,addMethod()addMethods()是支持方法替换和添加的。最后的方法列表添加是放在addMethods_finish()中的,其中会有方法排序(使用的是std::stable_sort,其中的sortermethod_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添加到rweprotocols中,添加完成。

/***********************************************************************
* 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

四个问题都应该有答案了,看底层代码在内存优化方面,能少用一个变量就少用,能共用就共用,例如PointerUnionlist_array_tt两个类的设计。