preface

The author sorted out a series of low-level articles about OC, hoping to help you.

1. Alloc principle of OC object creation in iOS

2. Align the OC object memory of iOS

3. The underlying principle of iOS OC ISA

4. Structural analysis of OC source code of iOS

5. Source code analysis of OC method cache of iOS

6. Search principle of OC method of iOS

7. Resolution and message forwarding principle of OC method of iOS

8. Loading process of iOS DYLD

In the dyLD loading process, the transition from dyLD source to OBJC source is done in the objc_init function. I’ll start with objc_init.

1. objc_init

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

The _objc_init function is executed at runtime. The following is a brief description of each function.

  • Environ_init (): Reads environment variables that affect the runtime and are available in theXcodePrint out the Settings in.
  • Tls_init (): about threadskeyThe binding. Such as a destructor for thread data.
  • Static_init (): Initializes the calling systemc++Constructor of.
  • Lock_init (): This implements nothing, possiblyc++Level locks also fitocJust a guess.
  • Exception_init (): Exception_initialization, which monitors exception_callback. Exception_init (): Exception_initialization, which monitors exception_callback_objc_terminate.
  • _dyld_objc_notify_register:dyldLoad the mapping callback toobjcPhi is a function of phi, and so is this_objc_initThe most important function of all functions.

The map_images in the _dyLD_OBJC_Notify_register function is described in detail, and the class is loaded here.

1.1 map_images

void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); } void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { ..... Omit some code....... // Find all images with Objective-C metadata. hCount = 0; // Count classes. Size various table based on the total. int totalClasses = 0; int unoptimizedTotalClasses = 0; . Omit some code....... if (hCount > 0) { _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); }}Copy the code

The map_images_NOLock function is called in the map_images function, of which the _read_images function is the most important. The name of the function reads the image file into memory.

  • HList: indicates the information list of the mirror header file.
  • HCount: indicates the amount of metadata for all mirrors.
  • TotalClasses: Total number of classes.
  • UnoptimizedTotalClasses: Total number of classes that are not optimized

2. _read_images

Because there is so much code for _read_images, only part of the code can be parsed.

if (! doneOnce) { doneOnce = YES; . Omit some code.. if (DisableTaggedPointers) { disableTaggedPointers(); } initializeTaggedPointerObfuscator(); if (PrintConnecting) { _objc_inform("CLASS: found %d classes during launch", totalClasses); } // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); ts.log("IMAGE TIMES: first time tasks"); } // This is a misnomer: gdb_objc_realized_classes is actually a list of // named classes not in the dyld shared cache, whether realized or not. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h /*********************************************************************** * allocatedClasses * A table of all classes (and metaclasses) which have been allocated * with objc_allocateClassPair. **********************************************************************/ static NXHashTable *allocatedClasses = nil;Copy the code

DoneOnce is executed only once, and if it is the first time in, the code inside the parentheses is executed. InitializeTaggedPointerObfuscator () this function is optimized for TaggedPointer do. Gdb_objc_realized_classes and allocatedClasses are two hash tables. Gdb_objc_realized_classes is a table that stores classes that are not primarily in the shared cache. AllocatedClasses this table holds all classes and metaclasses that have been allocated memory. Gdb_objc_realized_classes contains allocatedClasses.

2.1 Remapping of all classes

// Discover classes.fix up unresolved future classes.mark bundle classes.for (EACH_HEADER) {// Discover class.fix up unresolved future classes.mark bundle classes.for (EACH_HEADER) { Classref_t * classList = _getObjc2ClassList(hi, &count); classref_t * classList = _getObjc2ClassList(hi, &count); if (! mustReadClasses(hi)) { // Image is sufficiently optimized that we need not call readClass() continue; } bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); for (i = 0; i < count; I++) {OS_dispatch_queue_concurrent, OS_xpc_object, NSRunloop, CF, Fundation, libdispatch, etc. Class CLS = (Class) classList [I]; NewCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); If (newCls! NewCls! NewCls! NewCls! = cls && newCls) { // Class was moved but not deleted. Currently this occurs // only when the new class resolved a Future class. // Non-lazily realize the class below. realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } } ts.log("IMAGE TIMES: discover classes");Copy the code

This code mainly reads the classref_t pointer to all classes from the compiled class list, iterates through the CLS class address, and reads the compiler’s class and metaclass information through the readClass function.

2.1.1 readClass function
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if  (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->superclass = nil; return nil; } // Note: Class __ARCLite__'s hack does not go through here. // Class structure fixups that apply to it also need to be // performed in non-lazy realization below. // These fields should be set to zero because of the // binding of _objc_empty_vtable, But OS X 10.8's dyld // does not bind shared cache absolute symbols as expected. // This (and the __ARCLite__ hack Below) can be removed // once the simulator drops 10.8 support. #if TARGET_OS_SIMULATOR if (CLS ->cache._mask) cls->cache._mask = 0; if (cls->cache._occupied) cls->cache._occupied = 0; if (cls->ISA()->cache._mask) cls->ISA()->cache._mask = 0; if (cls->ISA()->cache._occupied) cls->ISA()->cache._occupied = 0; #endif cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (Class newCls = popFutureNamedClass(mangledName)) { // This name was previously allocated as a future class. // Copy objc_class to future class's struct. // Preserve future's rw data block. if (newCls->isAnySwift()) { _objc_fatal("Can't complete future class request for '%s' " "because the real class is too big.", cls->nameForLogging()); } 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) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // assert(cls  == getClass(name)); assert(getClassExceptSomeSwift(mangledName)); } else { addNamedClass(cls, mangledName, replacing); addClassTableEntry(cls); } // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; } static void addNamedClass(Class cls, const char *name, Class replacing = nil) { runtimeLock.assertLocked(); Class old; if ((old = getClassExceptSomeSwift(name)) && old ! = replacing) { inform_duplicate(name, old, cls); // getMaybeUnrealizedNonMetaClass uses name lookups. // Classes not found by name lookup must be in the // secondary meta->nonmeta table. addNonMetaClass(cls); } else { NXMapInsert(gdb_objc_realized_classes, name, cls); } assert(! (cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here // assert(! cls->isRealized()); } static void addClassTableEntry(Class cls, bool addMeta = true) { runtimeLock.assertLocked(); // This class is allowed to be a known class via the shared cache or via // data segments, but it is not allowed to be in the dynamic table already. assert(! NXHashMember(allocatedClasses, cls)); if (! isKnownClass(cls)) NXHashInsert(allocatedClasses, cls); if (addMeta) addClassTableEntry(cls->ISA(), false); }Copy the code

In the readClass function, if (Class newCls = popFutureNamedClass(mangledName)) determines that classes that need to be processed in the future are entered into the if condition flow, which is not normally entered into this flow. However, the addNamedClass(CLS, mangledName, Replacing) was implemented. And addClassTableEntry (CLS); The addNamedClass function adds the name and address of the class to the total hash table gDB_objC_realized_classes. The addClassTableEntry function is a sub-hash table that adds the address of the class to allocatedClasses. This is the time to insert the address information of the class into the hash table. NewCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); If the newCls returned from the readClass function is not the same as the CLS retrieved from the readClass function (popFutureNamedClass), Because this is dealing with ro in RW). NewCls = newCls = newCls = newCls = newCls

    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }    

    ts.log("IMAGE TIMES: realize future classes");
Copy the code

But it’s not usually done. This is something that needs special treatment in the future. The best way to determine if the current CLS is a class that needs to be processed later is in the readClass function. If yes, read CLS data(), set ro/ RW (usually not), and insert class address information into gDB_objC_realized_CLASSES and allocatedClasses.

2.2 Fix the remapping of classes

// Fix up remapped classes // Class list and nonlazy class list remain unremapped. // Class refs and super refs are remapped for message dispatching. if (! NoClassesRemapped ()) {for (EACH_HEADER) {// Remap Class, Class *classrefs = _getObjc2ClassRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } // fixme why doesn't test future1 catch the absence of this? classrefs = _getObjc2SuperRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); }}}Copy the code

This code block fixes the remapping of classes by remapping unmapped classes and Super classes, where the remapped classes are non-lazy-loaded classes. This block of code is not normally executed.

2.3 Register SEL to namedSelectors for all methods

// Fix up @selector references static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->isPreoptimized()) continue; bool isBundle = hi->isBundle(); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i < count; i++) { const char *name = sel_cname(sels[i]); Sels [I] = sel_registerNameNoLock(name, isBundle); } } } ts.log("IMAGE TIMES: fix up selector references"); //==================== SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (! name) return (SEL)0; result = search_builtins(name); if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); if (namedSelectors) { result = (SEL)NXMapGet(namedSelectors, name); } if (result) return result; // No match. Insert. if (! namedSelectors) { namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, (unsigned)SelrefCount); } if (! result) { result = sel_alloc(name, copy); // fixme choose a better container (hash not map for starters) NXMapInsert(namedSelectors, sel_getName(result), result);  } return result; }Copy the code

This code takes the SEL of the method in the class from _getObjc2SelectorRefs into the SEL array to get the name of the method, and then calls __sel_registerName again with sel_registerNameNoLock, Register method names with the hash table of namedSelectors.

2.3 Register all protocols in the PROTOCOL_map table

// Discover protocols. Fix up protocol refs. For (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol; Class CLS = (Class) &objc_class_ $_Protocol; // CLS = Protocol; assert(cls); NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->isPreoptimized(); bool isBundle = hi->isBundle(); Protocol Protocol_t **protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); } } ts.log("IMAGE TIMES: discover protocols");Copy the code

2.4 Fix legacy function pointer calls

#if ! (defined(__x86_64__) && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) # define SUPPORT_FIXUP 0 #else # define SUPPORT_FIXUP 1 #endif #if SUPPORT_FIXUP // Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) { message_ref_t *refs = _getObjc2MessageRefs(hi, &count); if (count == 0) continue; if (PrintVtables) { _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch " "call sites in %s", count, hi->fname()); } for (i = 0; i < count; i++) { fixupMessageRef(refs+i); } } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endifCopy the code

This code block is not normally executed, where fixupMessageRef internally registers the commonly used Pointers to alloc, objc_msgSend and other functions, and fixes the new Pointers.

2.5 Implement non-lazy loading classes

// Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_t *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); // printf("non-lazy Class:%s\n",cls->mangledName()); if (! cls) continue; // hack for class __ARCLite__, which didn't get this above #if TARGET_OS_SIMULATOR if (cls->cache._buckets == (void*)&_objc_empty_cache && (cls->cache._mask || cls->cache._occupied)) { cls->cache._mask = 0; cls->cache._occupied = 0; } if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache && (cls->ISA()->cache._mask || cls->ISA()->cache._occupied)) { cls->ISA()->cache._mask = 0; cls->ISA()->cache._occupied = 0; } #endif addClassTableEntry(cls); if (cls->isSwiftStable()) { if (cls->swiftMetadataInitializer()) { _objc_fatal("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can't disallow all Swift classes because of // classes like Swift.__emptyarraystorage} // Implement all non-lazy-loaded classes (instantiate some information about the class object, such as RW) realizeClassWithoutSwift(CLS); } } ts.log("IMAGE TIMES: realize non-lazy classes");Copy the code

A non-lazily loaded class implements the +load class method. The rW operation on bits data() of the non-lazily loaded class is in the realizeClassWithoutSwift function.

2.5.1 realizeClassWithoutSwift
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()) return cls; assert(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? ro = (const class_ro_t *)cls->data(); if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // Normal class. Allocate writeable class data. rw = (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 // Choose an index for this class. // Sets cls->instancesRequireRawIsa if indexes no more indexes are available cls->chooseClassArrayIndex(); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex(), cls->isSwiftStable() ? "(swift)" : "", cls->isSwiftLegacy() ? "(pre-stable swift)" : ""); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. // This assumes that none of those classes have Swift contents, // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // for ObjC subclasses of Swift classes. supercls = realizeClassWithoutSwift(remapClass(cls->superclass)); metacls = realizeClassWithoutSwift(remapClass(cls->ISA())); #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; if (DisableNonpointerIsa) { // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true;  } else if (! hackedDispatch && ! (ro->flags & RO_META) && 0 == strcmp(ro->name, "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->superclass && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsa(rawIsaIsInherited); } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping cls->superclass = supercls; cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable. if (supercls && ! isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSize if it wasn't set already. cls->setInstanceSize(ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) { cls->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); } } // Propagate the associated objects forbidden flag from ro or from // the superclass. if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) || (supercls && supercls->forbidsAssociatedObjects())) { rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; } // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); } else { addRootClass(cls); } // Attach categories methodizeClass(cls); return cls; }Copy the code

If (ro->flags & RO_FUTURE) {if (ro->flags & RO_FUTURE) {if (ro->flags & RO_FUTURE) {if (ro->flags & RO_FUTURE); Method_array_t methods, property_array_t properties, and protocol_array_T protocols still have no values in rW. So let’s keep going

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
Copy the code

At this point, the class information of the parent class and metaclass is recursed. At this time, you can refer to isa’s bitmap to understand the recursion at this time. The code below inserts into the inheritance chain and isa’s bitchain

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
Copy the code

And the end point of the recursion is where the function starts

if (! cls) return nil; if (cls->isRealized()) return cls;Copy the code

Because the root class of the final parent class inherits NSObject, the root metaclass of the metaclass inherits NSObject, and the upper endpoint of NSObject is nil. Here’s a good illustration of all that.

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
Copy the code

The above code is a bidirectional linked list between the parent class and the child class. MethodizeClass is then executed.

2.5.2 methodizeClass function
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    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);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    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);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        assert(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}
Copy the code

MethodizeClass: method_list_t,property_list_t, and protocol_list_t are null in the RW fetched from CLS ->data(). It is necessary to obtain their values from ro in RW and assign values to methods,properties, and protocols in RW respectively. This is the real assignment of attributes in RW. This is done through the attachLists function.

2.5.3 attachLists function
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; //10 uint32_t newCount = oldCount + addedCount; //4 setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; // 10+4 memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (! list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; } else { // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1:0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }}Copy the code

As can be seen from the source code, the attachLists function has three cases of many-to-many, 0 to 1 and 1 to many respectively.

The memmove and memcpy functions are used to copy a certain length of memory contents. When the memory overlaps, memmove function can guarantee the correctness of the copy result, but memcpy cannot guarantee the correctness of the copy result. The result is the same when there is no memory overlap.Copy the code
  • Many-to-many: Expand the array firstmemmoveThe arraylist () function adds the old arraylist to the expanded array (memmove can remove overlapping memory), and then usesmemcpyThe function adds the list to be added in front of the old list. So the newly added list comes before the old list.
  • 0 to 1: Assigns a value directly to the list.
  • 1 to many: first determine whether the old array has a value, if the length of the array is only 1, and then expand, in the case of the old array has a value, put the old array in the last subscript value of the new array to save, and then usememcpyFunction to copy.

In addition to the attachLists function is called when loading class processing methods, attributes and protocols, it is also called in the addMethods function. Add attribute _class_addProperty, add protocol class_addProtocol, and attachCategories are all called.

2.6 Implementation of lazy loading classes

The main difference between a lazy-loaded class and a non-lazy-loaded class is whether the +load method is implemented. The lazy-loaded class is implemented when it is called, so lookUpImpOrForward is initialized when the class first calls a method. If you want to understand this function can look at the iOS OC method of finding principles of this article. There’s a piece of source code

if (! cls->isRealized()) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again } ================================ static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) { return realizeClassMaybeSwiftMaybeRelock(cls, lock, true); } static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked) { lock.assertLocked(); if (! cls->isSwiftStable_ButAllowLegacyForNow()) { // Non-Swift class. Realize it now with the lock still held. // fixme wrong  in the future for objc subclasses of swift classes realizeClassWithoutSwift(cls); if (! leaveLocked) lock.unlock(); } else { // Swift class. We need to drop locks and call the Swift // runtime to initialize it. lock.unlock(); cls = realizeSwiftClass(cls); assert(cls->isRealized()); // callback must have provoked realization if (leaveLocked) lock.lock(); } return cls; }Copy the code

Can see from the source, the method to find the process, if the current CLS without implementation, will be executed realizeClassMaybeSwiftAndLeaveLocked functions, will eventually perform to realizeClassMaybeSwiftMaybeRelock function. In this function, there are respectively non-swift class and Swift class judgment, the current is OC class, will eventually execute the realizeClassWithoutSwift function, and this function is also introduced above is rW and RO operation.

3. The last

Through the above a series of source code analysis of the class load the whole process, the next summary:

  • indyldLoading process fromdyldToo much toobjcIs in the_objc_initIn the function_dyld_objc_notify_registerFunction callback back.
  • through_read_imagesFunction to readThe mirrorThe contents of the file are put into memory and initialized separately during and after the first entrygdb_objc_realized_classesandallocatedClassesTwo hash tables in which to load all classes ingdb_objc_realized_classesThe class that allocates memory will also be in the tableallocatedClassesIn the table.
  • Remap all classes.
  • All of theSELAll registered tonamedSelectorsIn the table.
  • Fixed function pointer legacy.
  • Will allProtocolRegistered toprotocol_mapTable.
  • For all theProtocolRemap.
  • Initialize all non-lazy-loaded classes, proceedrwandroOperation.
  • Iterate over the marked lazy-loaded class and initialize it.
  • Deal with allCategory, includingClassandMetaClass.