2019-10-10

In reading the Runtime source code (a preliminary approach to implementing object Orientation), you get an overview of Runtime as a whole from the data structures and apis exposed by the Runtime. h header file in Cocoa framework. This article begins with a detailed analysis of Apple’s open source Runtime source code. This article describes how Runtime implements classes and objects using C constructs, which should be the core of Runtime code.

Note: Github’s 1000-star RetVal/ Objc-Runtime project is version 750. The latest public version is version 756, which has changes in ARC support, ivarLayout definition, Swift compatibility, etc.

1. Definition of class

The essence of a class in Objective-C is an objc_class structure defined with the following code containing the following members:

  • isa:objc_classinheritanceobjc_objectStructure, therefore also containsisaPointers, whose main function is to point to the type of an object. In the new version of runtime,isaThe pointer doesn’t have to beClassThe type is a bitmap containing 64-bit data, in4.1In detail;
  • superclass: pointer to the parent class, used to organize the inheritance chain of the class;
  • cacheThe: class uses hash table data structures to cache recently called methods to improve method lookup efficiency (TODO: a separate article later);
  • bits:class_data_bits_tThe type of a structure used to record and store data about a classclass_rw_tThe memory address of the structure. throughdate()Methods to accessbitsThe valid bit field points to the memory space that is returnedclass_rw_tStructure;setData(class_rw_t *newData)Used to set thebitsThe value of the;

Note: The above bitmap is not a bitmap of a picture. Instead, data is treated as simple binary numbers, and some or all of the bits are assigned a special meaning. Bits or sets of bits that collectively represent a meaning are called bitfields.

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) { bits.setData(newData); } // Class methods are described in detail in the third part of this article... };Copy the code

Second, the data of class

The data for a class is stored primarily in the class_data_bits_t structure, which has a single bits pointer. The data() method of objc_class is used to obtain the class_rw_T structure address stored in the FAST_DATA_MASK field of bits. The class data is stored in the class_rw_t structure, and the rest is stored in the class_ro_t structure to which the ro pointer points.

In the names of class_rw_t and class_ro_T structures, rw stands for read write, and ro stands for read only. You can see that class_ro_t stores read-only information about the class. The information cannot be changed after the class is registered. Take, for example, the list of member variables for a class (which is stored in the class_ro_t structure). If the application class has been registered in memory and several instances have been constructed using the class, adding member variables will inevitably require reallocating memory for these classes in memory, which is quite expensive. Taking it to the extreme, adding a member variable to the root class NSObject would require almost all objective-C objects in memory to be reallocated, which is an unacceptable amount of computation at run time.

#if ! __LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#elif 1
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif

#if (! __LP64__ || TARGET_OS_WIN32 || \(TARGET_OS_SIMULATOR && ! TARGET_OS_IOSMAC))# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif

#if !SUPPORT_INDEXED_ISA  &&  !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif

struct class_data_bits_t {
    uintptr_t bits;

private:
    bool getBit(uintptr_t bit)
    {
        returnbits & bit; }... Public: // Get the class data class_rw_t*data() {
        return(class_rw_t *)(bits & FAST_DATA_MASK); } // Set the class data to voidsetData(class_rw_t *newData) {// Calls are allowed only during class registration, constructionsetData assert(! data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; }... // Whether the non-pointer ISA type is supported is described in detail in 4.1 Introduction to THE ISA Pointer of Objects#if FAST_REQUIRES_RAW_ISA
    bool instancesRequireRawIsa() {
        return getBit(FAST_REQUIRES_RAW_ISA);
    }
    void setInstancesRequireRawIsa() {
        setBits(FAST_REQUIRES_RAW_ISA);
    }
#elif SUPPORT_NONPOINTER_ISA// Mainstream models usually go to this compile branch boolinstancesRequireRawIsa() {
        return data()->flags & RW_REQUIRES_RAW_ISA;
    }
    void setInstancesRequireRawIsa() {
        data()->setFlags(RW_REQUIRES_RAW_ISA);
    }
#else
    bool instancesRequireRawIsa() {
        return true;
    }
    void setInstancesRequireRawIsa() {
        // nothing
    }
#endif. };Copy the code

1.1 Class_rw_t structure

The main data of the class is stored in bits. Bits use bitmaps to store the class_rw_T structure, which is used to record the key data of the class, such as the list of member variables, method list, attribute list, and protocol list. Class_rw_t contains only three basic bit operation methods. Class_rw_t contains the following members:

  • flags: 32-bit bitmap, marking the status of the class;
  • version: The type of the tag class,0Indicates that the class is non-metaclass,7Represents a class as a metaclass;
  • ro: Saves the read-only data of the class, after registering the classroThe data in is marked read-only, and the list of member variables is stored inro;
  • methods: A list of methods with their typesmethod_array_tFor a two-dimensional array container (TODO: later in a separate article);
  • properties: property list, its typeproperty_array_tFor a two-dimensional array container (TODO: later in a separate article);
  • protocols: Indicates the protocol list and its typeprotocol_array_tIs a two-dimensional array container;
  • firstSubclassThe first subclass of the: class, andnextSiblingClassRecord the inheritance chain of all classes organized into inheritance tree;
  • nextSiblingClass: class’s next sibling;
  • demangledName: class name, classes from Swift contain some special prefixes,demangledNameIs the name of the processed class;
  • index: of objects that mark the classisaWhether it isindexType;
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && ! __LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif/ / setsetThe bit specified is voidsetFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags); Void void clearFlags(uint32_t clear) {OSAtomicXor32Barrier(clear, &flags); void void void (uint32_t clear) {OSAtomicXor32Barrier(clear, &flags); } / / setsetVoid changeFlags(uint32_tset, 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));
    }

};
Copy the code

The flags member of class_rw_t lists some important bitfield definitions. They are prefixed with RW_. These bitfields are still readable and writable after class registration.

/ * * * * * * * * * * * * * kind of flags of read/write a domain after registered * * * * * * * * * * * * * / / / class is the class has been registered#define RW_REALIZED (1<<31)// The class is an unparsed Future class#define RW_FUTURE (1<<30)// A class is an already initialized class#define RW_INITIALIZED (1<<29)// Class is the class being initialized#define RW_INITIALIZING (1<<28)// class_rw_t->ro is a copy of the heap of class_ro_t#define RW_COPIED_RO (1<<27)// A class is an unregistered class that is being built#define RW_CONSTRUCTING (1<<26)// A class is a class that has been built and registered#define RW_CONSTRUCTED (1<<25)// A class is a class in which the load method has already been called#define RW_LOADED (1<<23)
#if ! SUPPORT_NONPOINTER_ISA// Class is a class for which a possible instance may have an associated object // Under the default compilation option, this bit is not required, as all may have an associated object#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22)
#endif// A class is a class with an instance-related GC layout#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21)// A class is a class that prohibits the use of associated objects#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20)// Class is a class that is being registered but has not been registered yet#define RW_REALIZING (1<<19)
Copy the code

1.1 Class_ro_T structure

After class registration, important information such as memory size, list of member variables, and memory layout of member variables should be fixed. The data that needs to be marked as read-only after class registration is stored in the class_ro_t structure. The RO member of the class_rw_t structure is a pointer to this structure. The class_ro_t structure contains the following main members:

  • flags: a 32-bit bitmap that marks the state of a class. Need to pay attention toclass_ro_ttheflagsThe bit fields used and previously describedclass_rw_ttheflagsThe bit fields used are completely different;
  • instanceStart: Member variables of the class, the starting offset in the instance’s memory space;
  • instanceSize: The size of memory occupied by instances of the class;
  • ivarLayout: Member variable memory layout, marking which WORD in the memory space occupied by the instance holds the member variable data;
  • name: the name of the class;
  • baseMethodList: Base method list, a list of methods specified at class definition;
  • baseProtocols: Protocol list;
  • ivars: list of member variables;
  • weakIvarLayout: Weak member variable layout;
  • baseProperties: Base property list, a list of properties specified when the class is defined;
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endifconst uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; . method_list_t *baseMethods() const {return baseMethodList;
    }

    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            returnro; }}};Copy the code

Some of the more important bitfield definitions in the flags member of class_ro_t are listed below, all prefixed with RO_. These bitfields are marked as read-only after class registration.

/ * * * * * * * * * * * * * class read-only flags a domain after registered * * * * * * * * * * * * * / / / class is a metaclass#define RO_META (1<<0)// Class is the root class#define RO_ROOT (1<<1)// Class has CXX constructor/destructor#define RO_HAS_CXX_STRUCTORS (1<<2)// The class implements the load method //#define RO_HAS_LOAD_METHOD (1<<3)/ / hide class#define RO_HIDDEN (1<<4)
// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak
#define RO_EXCEPTION (1<<5)
// class has ro field for Swift metadata initializer callback
#define RO_HAS_SWIFT_INITIALIZER (1<<6)// The class is compiled with the ARC option#define RO_IS_ARC (1<<7)// Class has a CXX destructor, but no CXX constructor#define RO_HAS_CXX_DTOR_ONLY (1<<8)
// class is not ARC but has ARC-style weak ivar layout 
#define RO_HAS_WEAK_WITHOUT_ARC (1<<9)// The class disallows the use of associated objects#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10)

// class is in an unloadable bundle - must never be set by compiler
#define RO_FROM_BUNDLE (1<<29)
// class is unrealized future class - must never be set by compiler
#define RO_FUTURE (1<<30)
// class is realized - must never be set by compiler
#define RO_REALIZED (1<<31)
Copy the code

Note: In fact, during class construction, the class_rw_t operation is sometimes used to remove some of the RO_ prefix bit fields in flags, but only in overlapping 29/30/31 bit fields.

Third, the behavior of the class

This chapter describes the methods defined in the objc_class structure.

3.1 Behavior related to the class loading process

The process of dynamically creating a class by calling the Runtime API consists of three steps:

  • callClass objc_allocateClassPair(...)Build a class;
  • Add necessary elements such as member variables, methods, etc.
  • callvoid objc_registerClassPair(Class cls)Registered classes;

However, the runtime process of loading a class from an image will be more elaborate and will be marked as a different type at different stages of loading the class (still an objc_class structure, only with different flags). For example: Future Class (Lazy loading class), Remapped Class (Remapped class), Realized Class (Realized class), Allocated Class (Named Class), Loaded Class Such as class (loaded class) and Initialized class (initialized class). Next, focus on Future class, Remelementary Class, and Realized class. Allocated class and named class are simply added to the globally managed hash table. Therefore, they are inserted into the Future class and Remapped class. Loaded class and Initialized class are classes on which the load method has been executed and classes on which the Initialize () method has been executed respectively.

The function code related to the class loading process in the objc_class structure is as follows. The RW_ and RO_ prefix bitfields correspond to the flags of class_rw_t and class_ro_t:

// Query whether the bool is initializingisInitializing() {
        returngetMeta()->data()->flags & RW_INITIALIZING; } // declare initializing (initializing) voidsetInitializing() { assert(! isMetaClass()); ISA()->setInfo(RW_INITIALIZING); } // Indicates whether the bool has been initializedisInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }

    void setInitialized(){ Class metacls; Class cls; assert(! isMetaClass()); cls = (Class)this; metacls = cls->ISA(); / / about alloc/dealloc/Retain/Release judgment and handling of special methods, such as... metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING); } boolisLoadable() {
        assert(isRealized());
        return true;  // any class registered for+load is definitely loadable}getLoadMethod() { runtimeLock.assertLocked(); const method_list_t *mlist; assert(isRealized()); assert(ISA()->isRealized()); assert(! isMetaClass()); assert(ISA()->isMetaClass()); IMP mlist = ISA()->data()->ro->baseMethods();if (mlist) {
            for (const auto& meth : *mlist) {
                const char *name = sel_cname(meth.name);
                if (0 == strcmp(name, "load")) {
                    returnmeth.imp; }}}returnnil; } // Runtime knows whether class bool is knownisRealized() {
        returndata()->flags & RW_REALIZED; } future Class boolisFuture() { 
        return data()->flags & RW_FUTURE;
    }
Copy the code

3.1.1 future class

The isFuture() function of objc_class, which determines whether a class is a Future class. This section explores the concept of future classes step by step through code, which is important for understanding the class loading process.

3.1.1.1 Future Class generation

First look at how the Future class is generated. The addFutureNamedClass(const char *name, Class CLS) function configures the CLS argument to a Future Class named:

  • distributionclsThe requiredclass_rw_t,class_ro_tMemory space;
  • willclsSet the class name ofname;
  • willclass_rw_ttheRO_FUTUREPosition one,RO_FUTUREIs equal to theRW_FUTURE;
  • In order tonameIs the keyword, willclsAdd to a global hash tablefutureNamedClasses;
static NXMapTable *future_named_class_map = nil;
static NXMapTable *futureNamedClasses()
{
    runtimeLock.assertLocked();
    
    if (future_named_class_map) return future_named_class_map;

    // future_named_class_map is big enough for CF’s classes and a few others
    future_named_class_map = 
        NXCreateMapTable(NXStrValueMapPrototype, 32);

    return future_named_class_map;
}

static void addFutureNamedClass(const char *name, Class cls)
{
    void *old;

    class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    ro->name = strdupIfMutable(name);
    rw->ro = ro;
    cls->setData(rw); cls->data()->flags = RO_FUTURE; old = NXMapKeyCopyingInsert(futureNamedClasses(), name, cls); assert(! old); }Copy the code

Trace call addFutureClass(…) Class objc_getFutureClass(const char *name) is not called in the Runtime source code. The popFutureClass(…) used to get the Future class from the namedFutureClasses hash table The function is indirect via readClass(…) Functions are called extensively. Therefore, the logic for building future classes is mostly hidden in runtime’s internal implementation, and only the logic that uses Future classes is open source.

Class objc_getFutureClass(const char *name)
{
    Class cls;

    cls = look_up_class(name, YES, NO);
    if (cls) {
        if (PrintFuture) {
            _objc_inform("FUTURE: found %p already in use for %s", 
                         (void*)cls, name);
        }

        returncls; } // Build a Future class if you can't find a class named namereturn _objc_allocateFutureClass(name);  
}

Class _objc_allocateFutureClass(const char *name)
{
    mutex_locker_t lock(runtimeLock);

    Class cls;
    NXMapTable *map = futureNamedClasses();

    if((CLS = (Class)NXMapGet(map, name))) {// There is a Future Class named namereturncls; } CLS = _calloc_class(sizeof(objc_class)); Add futureNamedClasses (name, CLS);return cls;
}
Copy the code
3.1.1.2 Future Class apps

addFutureClass(…) The action is obviously the process of logging the Future class globally, and then tracing when the future class was used. Static Class popFutureNamedClass(const char *name) popFuturenamedClasses (const char *name) This is the only entry point for the Future Class that gets the global record.

static Class popFutureNamedClass(const char *name)
{
    runtimeLock.assertLocked();

    Class cls = nil;

    if (future_named_class_map) {
        cls = (Class)NXMapKeyFreeingRemove(future_named_class_map, name);
        if(cls && NXCountMapTable(future_named_class_map) == 0) { NXFreeMapTable(future_named_class_map); future_named_class_map = nil; }}return cls;
}
Copy the code

PopFutureNamedClass is called in Class readClass(Class CLS, bool headerIsBundle, bool headerIsPreoptimized), which is used to read CLS Class data. The key processing logic is expressed as follows:

  • iffutureNamedClassesHash table existscls->mangledName()Class name of the future class, thenclsRemapping to the new classnewCls(The specific remapping process is in3.1.2), and then willnewClsRemapped class (ElementaryclsAdded to the global record for the keywordremappedClasses()Hash table;
  • willclsMarked as named class tocls->mangledName()Class name keyword added to global recordgdb_objc_realized_classesIn the hash table, indicating that runtime can start by looking up classes by class name (note that metaclasses do not need to be added);
  • willclsAllocated Class and its metaclass are allocated and both are added to the global recordallocatedClassesHash table (no keyword required), indicating that fixed memory space has been allocated for the class;

Note: Passing readClass(…) The CLS argument is of type Class, and the result of the function is Class. Why is reading Class information a weird process like “reading Class information from Class”? That’s because the CLS argument comes from Runtime’s open source process of reading classes from an image, which outputs objc_class in a special way: The output is either a Future class or a normal class, but its bits point to the class_ro_t structure instead of class_rw_t. This is true because the mirror reads the compiled static data, which should be stored in the class_ro_t structure.

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); // If there is a class that is not the root class (RO_ROOT bit is 0) and does not have a superclass, then missingWeakSuperclass is the only entry that adds nil value to remappedClassesif (missingWeakSuperclass(cls)) {
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        returnnil; } / / compatible with old version libobjc configuration, negligible CLS - > fixupBackwardDeployingStableSwift (); Class replacing = nil;if(Class newCls = popFutureNamedClass(mangledName)) {// Build newCls and copy the contents of CLS into it. Class_rw_t *rw = newCls->data(); class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro; memcpy(newCls, cls, sizeof(objc_class)); rw->ro = (class_ro_t *)newCls->data(); newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    
    if(headerIsPreoptimized && ! Replacing) {// Named class Assert (getClassExceptSomeSwift(mangledName)) already exists for the class name; }else{// Add classes to named classes addNamedClass(CLS, mangledName, Replacing); Allocated Classes addClassTableEntry(CLS); } // Set the RO_FROM_BUNDLE bitif (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}
Copy the code

From the above readClass (…). If (Class newCls = popFutureNamedClass(mangledName)) free((void *)old_ro); The RO data for the Future class is completely discarded. Finally, combined with all the above code, we can conclude the following:

  • The only valid data for a Future Class is actually the class name andrw.rwThe data function in is also very little, only useflagstheRO_FUTURE(= in factRW_FUTURE) The tag class is Future Class;
  • The Future class is used to allocate memory space for the specified class namereadClass(...)When a function reads a class, data is officially written to it. Future Classes are used to support lazy class loading;

3.1.2 remapped class

(remappedClass) (remappedClass) (remappedClass) (remappedClass) (remappedClass) (remappedClass) (remappedClass) (remappedClass) (remappedClass) Class readClass(Class CLS, bool headerIsBundle, bool headerIsPreoptimized) ¶

// 1. If the class name has been marked as a Future class, the future class corresponding to the class name will be assigned to newClsif(Class newCls = popFutureNamedClass(mangledName)) {// 2.rw record future Class rw class_rw_t *rw = newCls->data(); // 3. Const class_ro_t *old_ro = rw->ro; // 4. Copy data from CLS to newCls. Memcpy (newCls, CLS, sizeof(objc_class)); Rw ->ro = (class_ro_t *)newCls->data(); // 6. Use future class rW, CLS ro newCls->setData(rw); FreeIfMutable ((char *)old_ro->name); // 3. FreeIfMutable ((char *)old_ro->name); free((void *)old_ro); // 8. Add newCls to remappedClasses addRemappedClass(CLS, newCls); replacing = cls; cls = newCls; }Copy the code

After CLS is remapped to newCls, data in newCls retains superclass and cache members in CLS. But the bit field (FAST_DATA_MASK) in bits that points to the address of the class_rw_t structure points to the new class_rw_t structure. The ro pointer to the class_ro_t structure is stored in the memory space pointed to by CLS ->data(). The rest of the data follows directly from the Future class’s class_rw_t structure (returned by the Future class’s data() method) that pops up from the namedFutureClasses hash table.

Note: Although the data() method of objc_class is declared to return class_rw_t *, at its core it simply returns the memory address stored in the bit field of the FAST_DATA_MASK flag of the bits member of objc_class, which can actually hold any type of data. Class readClass(Class CLS, bool headerIsBundle, bool headerIsPreoptimized) ¶ The FAST_DATA_MASK bit field of the bits member of CLS points to the memory space that holds the class_ro_t structure, not the usual class_rw_t structure.

Static class remapClass(class CLS) ¶ If the class is not in the remappedClasses hash table, return the CLS itself; Static void remapClassRef(Class *clsref) remaps Class* clsref (clsref) to clsref (clsref); The remapping code for the class is as follows:

Static NXMapTable *remappedClasses(bool create) {static NXMapTable *remappedClasses(bool create) { Static NXMapTable *remapped_class_map = nil; runtimeLock.assertLocked();if (remapped_class_map) return remapped_class_map;
    if(! create)returnnil; // remapped_class_map is big enough to hold CF's classes and a few others INIT_ONCE_PTR NXCreateMapTable(NXPtrValueMapPrototype, 32), NXFreeMapTable(v));returnremapped_class_map; } // insert newcls into remappedClasses with oldcls as keyword // What the code reveals is, Static void addRemappedClass(class oldcls, class oldcls, class oldcls) Class newcls) { runtimeLock.assertLocked();if (PrintFuture) {
        _objc_inform("FUTURE: using %p instead of %p for %s", (void*)newcls, (void*)oldcls, oldcls->nameForLogging()); } void *old; old = NXMapInsert(remappedClasses(YES), oldcls, newcls); assert(! old); } // Get CLS remap class // Note: When remappedClasses is empty or the 'CLS' keyword does not exist in the hash table, return 'CLS' itself, Otherwise return 'CLS' remapped Class static Class remapClass(Class CLS) {runtimelock.assertlocked (); Class c2;if(! cls)return nil;

    NXMapTable *map = remappedClasses(NO);
    if(! map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {return cls;
    } else {
        returnc2; } // Remap a pointer to a Class, Static void remapClassRef(Class *clsref) {runtimelock.assertlocked (); Class newcls = remapClass(*clsref);if(*clsref ! = newcls) *clsref = newcls; }Copy the code

Finally, the following conclusions are summarized:

  • Future Class remapping returns the new class, saved inremappedClassesGlobal hash table;
  • Normal class remapping returns the class itself;
  • The real purpose of remapping is to support lazy loading of a class. Lazy loading of a class is stored as a Future classreadClassBefore the class data is officially loaded.

3.1.3 realized the class

Call readClass (…). Reading the class data only loads the class_ro_t static data of the class, so you still need to configure the class_rw_t structure of objc_class. This process is known as class realizing, or “realizing”. Specifically include:

  • configurationclass_rw_ttheRW_REALIZED,RW_REALIZINGA;
  • According to theclass_ro_ttheRO_METABit value, configurationclass_rw_ttheversion;
  • As the statically loaded superclass and metaclass may be remapped, it is necessary to ensure that the superclass and metaclass achieve class recognition;
  • configurationclass_rw_tthesuperclass;
  • Initialize theobjc_classtheisaPointer;
  • configurationivarLayout,instanceSize,instanceStart. This step is very important. The new version of Runtime supports non-Fragile Instance VariablesinstanceStart,instanceSizeIt will depend on the superclassinstanceSizeDynamic adjustment and need to be WORD aligned (TODO: more on this in a separate article);
  • configurationclass_rw_ttheRO_HAS_CXX_STRUCTORS,RO_HAS_CXX_DTOR_ONLY,RW_FORBIDS_ASSOCIATED_OBJECTS;
  • Add subclass/root class;
  • willclass_ro_tThe basic method list, attribute list, protocol list, and method list in the category of the class are added toclass_rw_t(TODO: more on this in a separate article);

Realizing class realizing code is mainly in static class realizeClassWithoutSwift(class CLS), just need to know the rough process. The specific code and comments are as follows:

static Class realizeClassWithoutSwift(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if(! cls)return nil;
    if (cls->isRealized()) returncls; assert(cls == remapClass(cls)); Ro = (const class_ro_t *) CLS ->data(); ro = (const class_ro_t *) CLS ->data();ifRw = CLS ->data(); (ro->flags & RO_FUTURE) {rw = CLS ->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); }elseRw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw); } isMeta = ro->flags & RO_META; rw->version = isMeta ? 7:0; // Old Runtime went up to 6 // Omit CLS ->chooseClassArrayIndex(); Realizing supercls = realizeClassWithoutSwift(remapClass(CLS ->superclass)); Realizing metacls = realizeClassWithoutSwift(CLS ->ISA());#if SUPPORT_NONPOINTER_ISA// Configure the RW_REQUIRES_RAW_ISA bit. Can be ignored. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited =false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        instancesRequireRawIsa = true;
    }
    else if(! hackedDispatch && ! (ro->flags & RO_META) && 0 == strcmp(ro->name,"OS_object")) 
    {
        hackedDispatch = true;
        instancesRequireRawIsa = true;
    }
    else if (supercls  &&  supercls->superclass  &&  
             supercls->instancesRequireRawIsa()) 
    {
        instancesRequireRawIsa = true;
        rawIsaIsInherited = true; } // Configure the RW_REQUIRES_RAW_ISA bitif (instancesRequireRawIsa) {
        cls->setInstancesRequireRawIsa(rawIsaIsInherited);
    }
#endifCLS ->superclass = supercls; // Superclass = supercls; cls->initClassIsa(metacls); / / ivarLayout adjustmentif (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 调整instanceSize
    cls->setInstanceSize(ro->instanceSize); / / ignoreif (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor(); }} // Ignoreif((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) || (supercls && supercls->forbidsAssociatedObjects())) { rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; } // Add subclass/root classif (supercls) {
        addSubclass(supercls, cls);
    } else{ addRootClass(cls); } // rw needs to save some data in ro, such as ro base method list, attribute list, protocol list // rw also needs to load the classification method list methodizeClass(CLS);returncls; } static void methodizeClass(Class cls) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro; Method_list_t *list = ro->baseMethods();if(list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); rw->methods.attachLists(&list, 1); } property_list_t *proplist = ro->baseProperties;if(proplist) { rw->properties.attachLists(&proplist, 1); } // Add the protocol list from roto the protocol list from RW. Protocol_list_t *protolist = ro->baseProtocols; // Add the protocol list from roto the protocol list from RW.if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    if(CLS ->isRootMetaclass()) {addMethod(CLS, SEL_initialize, (IMP)& objc_noop_IMP,"", NO); } / / the classification methods in the list to add to the list of rw method category_list * cats = unattachedCategoriesForClass (CLS,true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/); if (PrintConnecting) { if (cats) { for (uint32_t i = 0; i < cats->count; i++) { _objc_inform("CLASS: attached category %c%s(%s)", isMeta ? '+':'-', cls->nameForLogging(), cats->list[i].cat->name); } } } if (cats) free(cats); }Copy the code

In conclusion, by the end of class realizing, the class loading process is roughly as shown in the figure below. The future class column contains four steps: “Add the lazy class -> load the lazy class information -> remap the lazy class -> recognize the lazy class”; Normal Class column is the loading process of ordinary non-lazy loading classes. It only goes through two steps: “Loading class information -> Knowing the class”.

Realizing the import () method of the class will be executed in the process of execution. Finally, when the method of the class is called for the first time in the program run (implement logic in IMP lookUpImpOrForward(…) If the class is not initialized, the initialize() method of the class needs to be executed first. At this point, the class is officially loaded.

Note: The final class initializing should not strictly be part of the class loading process and can be classified as a separate class initialization phase. The loading of the class is complete when the load() method executes.

3.2 Basic status-related behaviors

The code for querying the basic status of classes in the objc_class structure is as follows. Notice when Class getMeta() retrieves metaclasses: for metaclasses, getMeta() returns a different result than ISA(), and for non-metaclasses, they are the same.

    bool isARC() {
        return data()->ro->flags & RO_IS_ARC;
    }

    bool isMetaClass() {
        assert(this);
        assert(isRealized());
        return data()->ro->flags & RO_META;
    }

    bool isMetaClassMaybeUnrealized() {
        return bits.safe_ro()->flags & RO_META;
    }

    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    const char *mangledName() { 
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    
    const char *demangledName();
    const char *nameForLogging();
Copy the code

3.3 Memory allocation behavior

When building an object according to the information of the class, it is necessary to allocate memory space for the data of member variables according to the memory layout of all member variables on the inheritance chain of the class. The size of allocated memory space is fixed and is aligned with WORD. Calling size_t class_getInstanceSize(Class CLS) actually calls the uint32_t alignedInstanceSize() function of the Objc_class structure.

Member variables are also offset in the instance memory space and are WORD aligned. The offset in the instance space for the first member variable is actually obtained by calling the uint32_t alignedInstanceStart() function of the objc_class structure.

The code for an objc_class function that involves memory allocation is as follows:

// The instance of the class may have a member variable start address that is not WORD aligned with uint32_tunalignedInstanceStart() {
        assert(isRealized());
        returndata()->ro->instanceStart; } // Set the start address of the class instance to uint32_t as WORDalignedInstanceStart() {
        returnword_align(unalignedInstanceStart()); } // The class instance size may not be aligned to WORD uint32_t because of ivar alignment valuesunalignedInstanceSize() {
        assert(isRealized());
        returndata()->ro->instanceSize; } // Configure the instance size of the class to align with uint32_t according to WORDalignedInstanceSize() {
        returnword_align(unalignedInstanceSize()); Size_t instanceSize(size_t extraBytes) {size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects to be at least 16 bytes. (TODO: requires why)if (size < 16) size = 16;
        returnsize; } // Set the class instance size to voidsetInstanceSize(uint32_t newSize) {
        assert(isRealized());
        if(newSize ! = data()->ro->instanceSize) { assert(data()->flags & RW_COPIED_RO); *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize; } bits.setFastInstanceSize(newSize); }Copy the code

Four, objects,

The data structure of an object is the objC_object structure. Objc_object contains only one isa pointer of type isa_t, unlike objc_object defined by

, where the isa pointer isa Class (pointing to an objc_class structure). This is because the new version of Runtime supports the non-pointer ISA structure. Isa is no longer a pointer to a Class but a 64-bit binary bitfield. Only some of the bitfields are used to hold the address of an object’s Class.

Objc_object contains the following types of methods:

  • isaOperation related,isaPoints to object types, which play a key role in controlling object construction, access to object member variables, response to object messages, and object memory management4.1Detailed introduction inisa;
  • Associated objects are related;
  • The weak reference is related to the object. After the object is released, you need to notify the weak reference to be set automaticallynil, so the object needs to know the addresses of all weak references;
  • Reference count correlation, support object reference count management;
  • deallocCorrelation, object destructor correlation, mainly to release the reference to the associated object;
  • Related to side Table, side Table is the core data structure of runtime managed object memory, including object memory reference count information, object weak reference information and other key data (TODO: later in a separate article);
  • Non-pointer types are supportedisaRelated;

The object definition code is as follows:

struct objc_object { private: isa_t isa; Public: // get the object type, assuming that the object is not tagged; Class ISA(); // GetInt (); // GetInt (); // GetInt (); // Initialize isa void initIsa(Class CLS /*nonpointer=false* /); void initClassIsa(Class cls /*nonpointer=maybe*/); void initProtocolIsa(Class cls /*nonpointer=maybe*/); void initInstanceIsa(Class cls, bool hasCxxDtor); // Set isa to point to the new type Class changeIsa(Class newCls); Bool hasNonpointerIsa(); bool hasNonpointerIsa(); // TaggedPointer related, ignore bool isTaggedPointer(); bool isBasicTaggedPointer(); bool isExtTaggedPointer(); Bool isClass(); Bool hasAssociatedObjects(); voidsetHasAssociatedObjects(); Bool isWeaklyReferenced(); bool isWeaklyReferenced(); voidsetWeaklyReferenced_nolock(); // Whether the object contains the.cxx constructor/destructor bool hasCxxDtor(); // reference count correlation id retain(); void release(); id autorelease(); // Reference count related implementation ID rootRetain(); bool rootRelease(); id rootAutorelease(); bool rootTryRetain(); bool rootReleaseShouldDealloc(); uintptr_t rootRetainCount(); // Dealloc implements bool rootIsDeallocating(); void clearDeallocating(); void rootDealloc(); private: void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor); id rootAutorelease2(); bool overrelease_error();#if SUPPORT_NONPOINTER_ISA// Support non-pointer isa ID rootRetain(bool tryRetain, bool handleOverflow); bool rootRelease(bool performDealloc, bool handleUnderflow); id rootRetain_overflow(bool tryRetain); bool rootRelease_underflow(bool performDealloc); void clearDeallocating_slow(); void sidetable_lock(); void sidetable_unlock(); void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced); bool sidetable_addExtraRC_nolock(size_t delta_rc); size_t sidetable_subExtraRC_nolock(size_t delta_rc); size_t sidetable_getExtraRC_nolock();#endif// Side-table related operations bool sidetable_isDeallocating(); void sidetable_clearDeallocating(); bool sidetable_isWeaklyReferenced(); void sidetable_setWeaklyReferenced_nolock(); id sidetable_retain(); id sidetable_retain_slow(SideTable& table); uintptr_t sidetable_release(bool performDealloc =true);
    uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);

    bool sidetable_tryRetain();

    uintptr_t sidetable_retainCount();
#if DEBUG
    bool sidetable_present();
#endif
};
Copy the code

Tagged Pointer: A Pointer to an object whose memory address is normally the value of Tagged Pointer writes the object’s class and data directly to the address.

4.1 Isa for objects

Isa for objc_object is mainly used to mark the types of objects. Isa in the new version of Runtime supports two types: pointer and non-pointer types. The former simply points to the class of the object. The latter is a 64-bit binary bitfield, which also includes the address of the object’s class. Other bitfields have special meanings. To support both forms, Runtime uses the ISA_T union to hold isa for objects.

Note: The members of the Union share memory space. In the case of isa_t, the CLS member and the bits member are different, but their values are actually the same at all times. For example, isa.class = [NSString class] specifies that CLS points to the memory address of the NSString class. If isa.bits = 0xFF, the isa.class value becomes 255.

4.1.1 ISA_T consortium

The ISA_T consortium has two members, Class CLS and uintPtr_T bits, which share 8 bytes of memory space (64-bit machines). The following is the source code for ISA_t, with the code for X86_64 and other architectures removed.

union isa_t 
{
    isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; // Requires compilation options to support non-pointer isa#if SUPPORT_NONPOINTER_ISA

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
    };
# endif

// SUPPORT_NONPOINTER_ISA
#endif
};
Copy the code

The following is the bit field distribution diagram of bits in the ARM64 architecture isa_T, with high left and low right. Side Table (TODO: a separate article) is used to manage all objective-C objects in memory. The most important one is object memory count management and object weak pointer management.

  • indexed: The magenta area is the first from the right.0saidisaIs the pointer type, the address of the storage class;1saidisaIs a non-pointer type. The lowest order distinction is usedisaType, because whenisaforClassWhen, its essence is pointingobjc_classPointer to the first address of the structure, becauseobjc_classMust be WORD aligned, that is, address must be an integer multiple of 8, so pointer typeisaAll the last three bits of must be0;
  • has_assoc: Magenta area, second from right. Mark whether the object has an associated object;
  • has_cxx_dtor: Third from right in magenta area. Indicates whether the object existscxxDestructor of the language family. Using pointer TypesisaThe tag is stored in the Side table.
  • shiftcls: The red area contains 33 digits. Saves the virtual memory address of the class, marks the type of the object (Core data);
  • magic: The yellow area contains six digits. Used of non-pointer typesisaCheck, in the ARM64 architecture these 6 bits are fixed values0x1a;
  • weakly_referenced: The cyan area is the first from the right. Marks whether the object is weakly referenced. Using pointer TypesisaThe tag is stored in the Side table.
  • deallocating: Second from right in the cyan area. Marks whether the object has been destructed. Using pointer TypesisaThe tag is stored in the Side table.
  • has_sidetable_rc: Third from right in the cyan area. Specifies whether to hold a reference count for this object with the side table.
  • extra_rc: The green area contains 19 digits. Record object reference counts inhas_sidetable_rcfor1To get the exact reference count of the object, you need to join the Side table.

Note: MSB is the Most Significant Bit, the extra_RC needs to handle overflows so it’s MSB, LSB is the Least Significant Bit, and indexed is used to determine the type of the ISA pointer so it’s LSB.

4.1.2 Isa Related Operations on Objects

4.1.2.1 ISA Construction

During object construction, initIsa(Class CLS, bool NonPointer, bool hasCxxDtor) of objC_Object is called directly or indirectly to build ISA, which is called internally by other initIsa methods. Where the CLS parameter indicates the class of the object, nonpointer indicates whether the non-pointer type ISA is constructed, and hasCxxDtor indicates whether the object has a CXX language destructor.

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(! isTaggedPointer());if(! Nonpointer) {// build a nonpointer type isa isa.cls = CLS; }else{// build a non-pointer type isa assert(! DisableNonpointerIsa); assert(! cls->instancesRequireRawIsa()); isa_t newisa(0);#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;

        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elseNewisa.bits = ISA_MAGIC_VALUE; // Magic is set to 0xA1, index is set to 1 newisa.has_cXX_dTOR = hasCxxDtor; Newisa. shiftcls = (uintptr_t) CLS >> 3;#endifNewisa = newisa; }}Copy the code
4.1.2.2 ISA Application

Non-pointer isa actually transfers some memory management data from side table (including partial memory reference counts, whether associated object markers are included, whether weak reference markers are included, and whether destroyed markers are included) to ISA. This reduces the number of side table queries for memory management related operations in objC_Object. Most of the methods of status query in objc_Object involve isa bit operation. This section uses weak reference related queries as an example.

  • IsWeaklyReferenced () is used to query whether an object isWeaklyReferenced. When isa isa non-pointer type, isa. Weakly_referenced is returned directly; otherwise, sidetable_isWeaklyReferenced is called to query the result from sidetable.

  • SetWeaklyReferenced_nolock () is used to set whether an object is weakly referenced. When the object isa isa non-pointer type, only weakly_referenced position is 1, otherwise, we need to call sidetable_setWeaklyReferenced_nolock() to query the result from the sidetable and write it.

inline bool
objc_object::isWeaklyReferenced() { assert(! isTaggedPointer());if (isa.nonpointer) return isa.weakly_referenced;
    else return sidetable_isWeaklyReferenced();
}


inline void
objc_object::setWeaklyReferenced_nolock() {// source setup weakly_referenced process is too convoluted; retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa;if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits); sidetable_setWeaklyReferenced_nolock();return;
    }
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.weakly_referenced = true;
    if(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; }Copy the code

4.2 Object construction

Objects are built essentially by calling _class_createInstanceFromZone(…) Function implementation, in which the most critical argument passed is CLS of type Class, meaning that an object of Class CLS is built. The code looks pretty long, but it actually contains only two operations:

  • Assign an objectcls->instanceSize()Size of memory space;
  • Building objectisa;
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if(! cls)return nil;

    assert(cls->isRealized());

    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if(! Zone && fast) {/ / -- -- -- -- -- -- -- -- -- -- - logic branch 1 -- -- -- -- -- -- -- -- -- -- - / / / / 1.1 allocate memory object obj = (id) calloc (1, size);if(! obj)returnnil; Obj ->initInstanceIsa(CLS, hasCxxDtor); }else{/ / -- -- -- -- -- -- -- -- -- -- - logic branch 2 -- -- -- -- -- -- -- -- -- -- - / / / / 2.1 objects allocated memoryif (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if(! obj)returnnil; // 2.2 build object isa obj->initIsa(CLS); } // If a CXX family constructor exists, call it. Can be ignoredif (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
Copy the code

4.3 Object destruction

The destructor of an object calls the rootDealloc() method on the object. The source code isn’t much, but the process goes through several functions. The operations required for object destruction are summarized as follows:

  • Release references to associated objects;
  • Clear the memory management data such as the weak reference address and object reference count of the object stored in the Side table.
  • Free the memory occupied by the object.
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present()); Free (this); }else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if(! obj)returnnil; objc_destructInstance(obj); Free (obj);return nil;
}

void *objc_destructInstance(id obj) 
{
    if(obj) { bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // If a CXX language destructor exists, call. Can be ignoredif(cxx) object_cxxDestruct(obj); // Release the associated objectif(assoc) _object_remove_assocations(obj); // clear the memory management data stored in the side table, such as the weak reference address and reference count of the object, obj->clearDeallocating(); // Ignore implementation details}return obj;
}
Copy the code

Five, the summary

  • In Runtime, objects are implemented with objc_object. Isa members of type ISA_T, which occupy 8 bytes of memory space, point to the object’s class. Isa in the new version of Runtime also holds the key data of object memory management, such as object reference count, whether the object has been destroyed, whether it is weakly referenced, and whether there is an associated object.

  • In Runtime, the class is implemented by objc_class. The class is also an object. The ISA of objc_class points to the metaclass of the class, the ISA of the metaclass points to the root metaclass, and the ISA of the root metaclass points to the root metaclass itself.

  • The superclass member of objc_class points to the superclass of the class and is used to organize the inheritance chain of the class.

  • The data of objc_class is stored in the memory space pointed to by the valid bitfield of the bits member. The compile-time decision data of the class is stored in the class_ro_t structure. The run-time decision data is stored in the class_rw_t structure. The flags members of class_ro_t and class_rw_t are used to mark the class status. The total data entry is class_rw_t.

  • There is a lazy loading mechanism for a class. When a class is lazy loading, it is marked as a Future class first, and then readClass(…) is called to load the data of the future class formally. Method to remap the Future class.

  • Classes loaded from the image contain only compile-time resolution data, so the bits members point to the class_ro_T data structure. InstanceSize, instanceStart, ivarLayout (to support non-Fragile Instance variables) will be adjusted by class realizing the class_rw_T data of the class. In addition, the basic method list, attribute list, protocol list and method list in class category in class_ro_t are added to class_rw_t.

  • The member variables, method list, attribute list, classification implementation and loading, and side table involved in object memory management will be described in detail in subsequent separate articles.