From the connection between DYld and objC at startup in the previous section, dyLD_INIT ends up in the _objc_init method when initializeMainExecutable is initialized, Return to dyLD_INIT and execute the main program.

Where _objc_init mainly performs class loading (the process of loading class information from RO to RW). _DYLD_OBJC_notify_register is the class’s registration method that tells DYLD to call the map_images, load_images, and unmap_image functions when appropriate. The map_images method loads and handles classes, selectors, protocols, categories, and so on.

Lazy-loaded and non-lazy-loaded classes

Classes are classified as lazy-loaded and non-lazy-loaded, and their loading timing is different.

Lazy-loaded classes: classes that are loaded when the first message is executed. Non-lazy-loaded classes (the focus of this article) are loaded when the program starts (internally implementing the +load method).

The following is the order of function methods executed by lazy-loaded and non-lazy-loaded classes.

If you want to delve deeper into the class loading process, check out the following

map_images

The job of map_images is to process a given image mapped by dyLD. Responsible for managing all symbols in files and dynamic libraries (classes, selectors, protocols, categories, etc.)

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

Call map_images_nolock in the map_images function.

void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { ...... If (hCount > 0) {_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } for (auto func : loadImageFuncs) { for (uint32_t i = 0; i < mhCount; i++) { func(mhdrs[I]); }}}Copy the code

read_images

Find all image files from machO, call _read_images, perform all class registration and repair functions. Image loading is called after all Settings are complete. So the operations before loading the image are clustered in _read_images, which is what we’re focusing on. The source code is very long, simplified as follows:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { ...... // ① Control to load only once (build table: the list of named classes not in dyLD shared cache, whether or not implemented) if (! doneOnce) { doneOnce = YES; int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); }... // fix @selector references. Static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) 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]); SEL sel = sel_registerNameNoLock(name, isBundle); if (sels[i] ! = sel) { sels[i] = sel; }}}} // ③ Fix unresolved future classes, Mark the Bundle class for (EACH_HEADER) {classref_t const *classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = (Class)classlist[I]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); if (newCls ! = cls && newCls) { resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}} // fix the remapped class; (some classes not loaded in the image file) if (! noClassesRemapped()) { for (EACH_HEADER) { Class *classrefs = _getObjc2ClassRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[I]); } classrefs = _getObjc2SuperRefs(hi, &count); for (i = 0; i < count; i++) { remapClassRef(&classrefs[I]); }} #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); // ⑥ readProtocol NXMapTable *protocol_map = protocols(); protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); for (EACH_HEADER) { for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }} / / 7 Fix up @ protocol references for (EACH_HEADER) {if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { remapProtocolRef(&protolist[I]); }} / / end load_categories_nolock if (didInitialAttachCategories) for (EACH_HEADER) with a {{load_categories_nolock (hi); } // ⑨ non-lazy loading for (EACH_HEADER) {classref_t const *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (! cls) continue; addClassTableEntry(cls); realizeClassWithoutSwift(cls, nil); ⑩ realize future classes ) // 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, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); } if (DebugNonFragileIvars) { realizeAllClasses(); }}Copy the code

The simplified summary is as follows:

  • ① : Condition control is only done once:

Create a table gdb_objC_realized_classes to store a list of named classes that are not in the dyLD shared cache, whether implemented or not

  • ② : Fix @selector references

[Fixed] SEL is a string with an address, even though the names of the two methods are the same, the addresses of the methods are not necessarily the same. Multiple frameworks may have an init method, which reads the address of a method in the system based on the offset of the frame in the main program and the offset of the method within the frame. So, we need to tweak at sign Selector.

  • ③ : Fix unresolved Future classes.

Load the list of classes from the MachO __objc_classList section, traverse the list of classes, do readClass, if the result of the readClass is different from the class in the list, do repair, but this usually does not occur, only the class has been moved and not deleted. In readClass, take the name of the class from CLS ->mangledName(), associate the name with the address, and insert it into the gDB_objC_realized_classes created in step 1. At the same time, insert the class and metaclass into the allocatedClasses table. In this step, readClass loads the address and name of the class into memory.

  • Fix remapping class:

If there are classes that have not been loaded by the image file, remap at this point.

  • ⑤ : Repair some old onesobjc_msgSend_fixupcall

Some old message fixes are enforced, such as alloc -> objC_alloc, allocWithZone -> objc_allocWithZone, etc.

  • 6: readProtocol:

Create a hash table storing proctol, read the protocol list from the [MachO] __objc_protolist section, iterate over the protocol list, perform readProtocol, add the protocol to the proctol table, register in memory.

  • ⑦ : Fix up @protocol References

The pre-optimized image may already have the correct protocol reference, but we can’t be sure, so a fix is done here to prevent the referenced protocol from being reassigned.

  • End: load_categories

Load classification, it is important to note that this does not load classification, only after didInitialAttachCategories assignment to true, And didInitialAttachCategories assignment to true process is in _dyld_objc_notify_register calls after the completion of the first load_images call assignment.

  • ⑨ : loading non-lazy classes

What is done here is the implementation of a non-lazy-loaded class, that is, a class that implements the +load method. AddClassTableEntry (CLS) reads a list of non-lazy classes from __objc_nlclsListSection (MachO). Insert non-lazy-loaded classes into the class table and load them into memory. In step 3, we load the class into memory with an address and name, and finally execute realizeClassWithoutSwift to complete the structure of the class.

  • ⑩:realize future classes

If there are future classes being processed, they need to be implemented here in case CF manipulates them. The implementation here is also through realizeClassWithoutSwift.

_read_images does much of this, but it can be simplified if you focus only on the loading process of the class:

  • Gdb_objc_realized_classes built form. Creating a table of named classes that are not in the shared cache does not matter whether the class is implemented or not.

  • Insert gDB_objC_realized_CLASSES and allocatedClasses into MachO __objc_classList. From there, the class is loaded into memory and has an address and a name.

  • Implement the details of the class (RO, RW, etc.) to ensure the structural integrity of the class.

Specific readClass and realizeClassWithoutSwift code analysis is as follows:

readClass

  • fromcls->mangledName()Gets the name of the class
  • If there is a parent missing or weak-linked in the inheritance chain, the class is ignored (it is incomplete) and return nil;
  • If the class is a previously allocated class for future processing, copy the objC_class data to the Future class, saving the future’srwThe data block;
  • ASSERT if it is a pre-optimized class and not a Future class; Otherwise, run addNamedClass to associate the address and name of the class and insert the GDB_objC_realized_classes table. Insert the allocatedClasses table with addClassTableEntry;
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); // If there is a parent missing or weak-linked condition in the inheritance chain, ignore the class directly.  return nil if (missingWeakSuperclass(cls)) { if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->superclass = nil; return nil; } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; // If the class is a previously allocated class for future processing, copy the objc_class data to the future class. Save the future RW block if (Class newCls = popFutureNamedClass(mangledName)) {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->set_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; } // ASSERT, if (headerIsPreoptimized &&! replacing) { ASSERT(getClassExceptSomeSwift(mangledName)); } else {replacing addresses and names from the GDB_objC_realized_classes table addNamedClass(CLS, mangledName, Replacing); // Add the allocatedClasses table 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; }Copy the code

Determine if a class with that name already exists in the gDB_objC_realized_classes table. If so, insert nonMetaClasses. Insert the GDB_objC_realized_CLASSES table if it does not exist

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

If the class is known at run time (such as in a shared cache, in a data segment with a loaded image, or already allocated with obj_allocateClassPair), then you do not need to insert the table; otherwise, you insert the allocatedClasses table, along with the class’s metaclass. The allocatedClasses table is created at runtime_init().

static void addClassTableEntry(Class cls, bool addMeta = true) { runtimeLock.assertLocked(); auto &set = objc::allocatedClasses.get(); ASSERT(set.find(cls) == set.end()); if (! isKnownClass(cls)) set.insert(cls); if (addMeta) addClassTableEntry(cls->ISA(), false); }Copy the code

At this point, the class in MachO is loaded into memory and has a name and address, but the data is not associated, and the data association is inrealizeClassWithoutSwift In.

realizeClassWithoutSwift

  • fromcls->data()If it is a future class, yesrwroThe assignment; If it is a normal class, then openrwSpace,roAssign and copy torwIn the.
  • Recursively implements the parent and metaclass of the class, and then updates the parent and metaclass of the class for remapping. Ensure the integrity of the inheritance chain and the ISA chain.
  • fromroAssigns some values torwIn the.
  • If the parent class exists, link the class to the subclass list of its parent class, otherwise as a new root class.
  • performmethodizeClass, set the class property list, method list, protocol list, add classification and so on.
static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; if (! cls) return nil; if (cls->isRealized()) return cls; Return ASSERT(CLS == remapClass(CLS)) if the class is implemented; auto ro = (const class_ro_t *)cls->data(); Auto isMeta = ro->flags & RO_META; if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else {// Normal class. Allocate writeable class data. Rw = objc::zalloc<class_rw_t>(); rw->set_ro(ro); / / set rw rw - > flags = RW_REALIZED | RW_REALIZING | isMeta; cls->setData(rw); } #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif 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)" : ""); } // Implement superclasses and metaclasses, if they are not already implemented. // For the root class, this is done after setting rw_realize above. For the root metaclass, this needs to be done after selecting the class index. supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA ...... #endif Update superclass and metaclass in case of remapping CLS ->superclass = supercls; cls->initClassIsa(metacls); 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 object disallow flag from ro or 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 // Connect this class to its superclass's subclass list if (supercls) {addSubclass(supercls, cls); } else { addRootClass(cls); } // Attach categories // execute methodizeClass, add attribute list, protocol list, method list etc. methodizeClass(cls, previously); return cls; }Copy the code

load_images

When the image file is mapped, load_images is then executed to handle the +load method for the image that has been mapped in dyLD.

void load_images(const char *path __unused, const struct mach_header *mh) { if (! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories = true; loadAllCategories(); } // Return without taking locks if there are no +load methods here. if (! hasLoadMethods((const headerType *)mh)) return; recursive_mutex_locker_t lock(loadMethodLock); // Discover load methods { mutex_locker_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } // Call load methods(without runtimelock-re-entrant) call_load_methods(); }Copy the code

First, if the image file of libobjc.a.dylib is mapped and load_images is executed for the first time, loadAllCategories() is called to complete the loading of all categories.

There are many dynamic libraries that the system depends on. Each dynamic library checks whether the __objc_nlclslist and __objc_nlcatList in the current dynamic library (MachO) are full when executing load_images. There is no direct return. If so, execute prepare_load_methods

prepare_load_methods

  • Read from the MachO header of the current dynamic library__objc_nlclslist, the implementation ofschedule_class_load;
  • Read from the MachO header of the current dynamic library__objc_nlcatlist, then forces the main class to implement, and then executeadd_category_to_loadable_list , will be executed+loadClassification add ofloadable_categoriesIn the table.
void prepare_load_methods(const headerType *mhdr) { size_t count, I; runtimeLock.assertLocked(); // Load classref_t const * classList = _getObjc2NonlazyClassList(MHDR, &count); for (i = 0; i < count; i++) { schedule_class_load(remapClass(classlist[i])); } / / classification load category_t * const * categorylist = _getObjc2NonlazyCategoryList (MHDR, & count); for (i = 0; i < count; i++) { category_t *cat = categorylist[I]; Class cls = remapClass(cat->cls); if (! cls) continue; // category for ignored weak-linked class if (cls->isSwiftStable()) { _objc_fatal("Swift class extensions and categories  on Swift " "classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls, nil); ASSERT(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}Copy the code

 call_load_methods()

Prepare_load_methods () adds classes, superclasses, and classes to the corresponding table, and then executes call_load_methods() to execute the +load method of the classes in the table.

void call_load_methods(void) { static bool loading = NO; bool more_categories; loadMethodLock.assertLocked(); // Re-entrant calls do nothing; the outermost call will finish the job. if (loading) return; loading = YES; void *pool = objc_autoreleasePoolPush(); Do {// Repeatedly load load until nothing happens // 1. Repeatedly call class +loads until there aren't any more while (loadable_classes_used > 0)  { call_class_loads(); } // load // 2. Call category +load ONCE more_categories = call_category_load (); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; }Copy the code
  • Enter thedo.... whilecycle
  • Data from previously stored tablesloadable_classesCan be executed+loadMethod for class data, to send messages, to execute+loadMethod,
  • fromloadable_categories Table fetching can be executed+loadMethod to classify data, carry out message sending, and execute+loadMethods.

conclusion

This article mainly starts with _objc_init, combined with map_images and load_images to explore the class loading process. Rw ->set_ro(ro); CLS ->setData(rW); rW ->setData(rW); rW ->setData(rW); rW ->setData(rW)

reference

Loading of iOS classes

IOS class loading on