An overview of the

Curious about class_rw_T memory optimization in Advancements in the Objective-C Runtime, here are some questions:

  • Problem a:class_rw_t,class_rw_ext_t,class_ro_tHow is the relationship between the three realized?
  • Question 2: Under what conditions will it be created and usedclass_rw_ext_t?
  • Question 3:class_rw_ext_twithclass_ro_tThere is a list of methods, attributes, and protocols in the. What is the difference in data?
  • Q4: how to dynamically add attributes, methods and protocols? What data is the operation?

class_rw_t

Here’s the class_rw_t code, not much, but the essence. A brief description follows.

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: /* Alias operation, Like 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")>; /* Get 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}; } /* Store class_ro_t to member 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); } /* * Assign class_ro_t to the member ro of class_rw_ext_t, */ 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: /* Operation on member variables' 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)); } /* Create, save, and fetch the member variable 'ro_or_rw_ext'; */ /* Get 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); } /* Get class_rw_ext_t, if not create one */ 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); } /* * Select ro from the class_rw_ext_t member variable to determine the type of the class_rw_ext_t member variable. */ 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); } /* Update 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); }} /* Get the list of methods, attributes, and protocols from the member variable '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}; }}}; /* * Create class_rw_ext_t from class_ro_t, * and add the class_ro_t method, attributes, and protocol list to the corresponding list in class_rw_ext_t. */ 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; }Copy the code

Explicit_atomic is an inherited C++ atomic that encapsulates a value type that is guaranteed to be accessed without data contention and can be used to synchronize memory access between different threads. Refer to the atomic summary for details

PointerUnion is a class that can store either class_rw_ext_T data or class_RO_T data. PointerUnion is a class that can store either class_rw_ext_T data or class_RO_T data. You can’t have your cake and eat it too. There is an IS () method to determine the data of a certain property type, and a get() method to get the data of a property type.

Member variable ro_OR_rw_ext

Explicit_atomic

RO_or_rw_ext; Is an atomic type of data that holds a pointer to class_rw_ext_t or class_ro_t.

methods

The methods can be roughly divided into three categories:

For member variablesflagsThe operation;

Some operations on flags, including data update and cleanup operations;

For member variablesro_or_rw_extCreate, save, fetch operations of;

PointerUnion is used to store Pointers to class_rw_ext_t or class_ro_t structures. Fetching data is also done via PointerUnion.

By member variablero_or_rw_extGet method, attribute, protocol list;

When fetching method, attribute, and protocol list data, obtain the member variable ro_OR_rw_ext first, determine whether its type is class_rw_ext_t or class_RO_T, and then fetch data from the corresponding list.

class_rw_ext_t

The data structure is as follows

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;
};
Copy the code

The data stored here is a list of methods, properties, protocols, dynamically changeable lists, and ro points to class_ro_t, with no additional methods.

class_ro_t

Class_ro_t, which stores the raw data of a class, including names, method lists, protocol lists, attribute lists, member variable lists, and so on.

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 { ...... }Copy the code

Explore problem 1: relationships

According to the above description, class_rw_t, class_rw_ext_t, class_ro_t should have two diagrams:

Do not use class_rw_ext_t

class_rw_tA member variable ofro_or_rw_extPoint directly toclass_ro_t, the relationship chain can be omittedclass_rw_ext_tStructure of data, less memory consumption.

Using class_rw_ext_t

class_rw_tA member variable ofro_or_rw_extPoint directly toclass_rw_ext_t.class_rw_ext_tMember variable ofroPoint to theclass_ro_t, the chain can not reduce memory consumption, do not look down upon these memory, every little makes a mickle.

Explore question 2: Create & Use conditions

create

createclass_rw_ext_tExternally, it is commonly usedextAllocIfNeeded()Method, below is the call chain of the method (there are some internal methods, not inruntime.hThe exposure)One of theobjc_class::demangledNameJust to clarify, if it’s not a metaclass, Swift class, go straight throughro()->getName(), otherwise passextAllocIfNeeded()createclass_rw_ext_tStructure, in the process of member variablesdemangledNameAssignment operation, and returnclass_rw_ext_tA member variable in a structuredemangledName. So both Swift classes and metaclasses are usedclass_rw_ext_t?

Conditions of use

As you can see from the above call chain, there are several places that trigger the creation of the class_rw_ext_t structure:

  • Dynamically adding properties
  • Dynamic addition method
  • Dynamic Add protocol
  • The current class has a category
  • Set the version for the class

Want to further reduce memory can be done through the above conditions.

Explore question 3: data differences

Entsize_list_tt and list_array_tt are two data structures to compare.

Methods list

Class_rw_ext_t stores the list of methods using method_array_t, which is a data inheriting from list_ARRAY_TT that stores method_list_t, In class_RO_T, the method list is stored using method_list_t, which is a normal array. When creating a class_rw_ext_T structure with class_RW_T ::extAlloc, attachLists are used to add method lists. The list pointer in method_array_t is used to point to method_list_t, and if you are adding methods (including dynamic adding and category methods), you will enable the storage as a two-dimensional array.

Property list

Property_array_t is used to store property lists in class_rw_ext_t, and property_list_t is used in class_ro_t. Data is added to lists in the same way as method lists.

Agreement list

In class_rw_ext_t, the protocol is used to store the list of attributes; in class_ro_t, the protocol_LIST_t is used to add data to the list in the same way as the list of methods.

Explore problem four: implementation of dynamic addition

Add attributes

The last thing that dynamically adds a property is called_class_addPropertyMethod, the detailed code is as follows:

/*********************************************************************** * 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); }Copy the code

_class_addProperty This method can be used to replace and add attributes. To add attributes, the extAllocIfNeeded() method is used to obtain the class_rw_ext_T structure pointer Rwe. Create a property list (property_list_t *) proplist, add properties to the property list, and finally add PROplist to the rWE member variable Properties. Below is a flowchart for adding attributes.

How to add attributes using Runtiem? For details, see several methods for dynamically adding attributes in ios

Add methods

Adding a method pattern is pretty much like adding a property,addMethod(),addMethods()Supports method substitution and addition. The last method list to add is putaddMethods_finish()In which there will be method sorting (usingstd::stable_sort, in which thesorterismethod_t::SortBySELAddress), clear method cache operation, process method list, and then add toclass_rw_ext_tIn the list of methods in.

PrepareMethodLists (), there are clear cache operations, there are methods sort, the last method scan is what?

Here is the implementation code:

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); }Copy the code

Add the agreement

Similar to adding attributes, first check whether the class exists and then determine whether the current class has complied with the protocol to be added. If the conditions are met, also use extAllocIfNeeded() to obtain the rwe of class_rw_ext_t and create protolist. The protocols to be added are added to Protolist, and finally, protolist is added to 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; }Copy the code

LAST

In terms of memory optimization, use as few variables as possible, and share as many as possible, such as PointerUnion and list_array_TT class design.