preface

In the last chapter, we learned about the loading process of the application program. IOS used dyLD to load the corresponding file into the memory, and then called the main function to start the App. We mainly studied the process during the period before main function. And when were the rW and RO written?

Source code analysis

To analyze the loading of a class, we still start with the source code, according to the execution of the source code to slowly drill down, analyze what is done at each stage. Start with the _objc_init method that App starts

_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();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

The _objc_init function does the following:

  • Environment variable initialization
  • Binding of thread keys
  • Run the C++ static constructor
  • Runtime Initializes the Runtime environment
  • Initialize libobJC’s exception handling system
  • The cache condition is initialized
  • Start the callback mechanism
  • Image file loading

Let’s look at the _dyLD_OBJC_Notify_register method and see how it loads the class.

_dyld_objc_notify_register

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

The above method contains calls to three methods, map_images, load_images, and unmap_image. Let’s look at map_images first.

map_images

/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/ 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

Map_images calls the map_images_NOLock method. Look at the definition of map_images_NOLock

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); }... // omit some code}Copy the code

The _read_images function is called from map_images_NOLock to read the image file. Next, look at the definition of the _read_images function.

_read_images

Through the reading _read_images method, the following 10 processes are mainly involved:

  • Condition control for a load
  • Fix the precompile phase@selectorThe chaotic problem of
  • Wrong messy class handling
  • Fixed remapping some classes that were not loaded by the image file
  • Fix some messages
  • Read protocols when there are protocols in the class
  • Fix protocols that were not loaded
  • Classification process
  • Class loading processing
  • For classes that are not processed, optimize those that are violated
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { ...... // omit some code if (! doneOnce) { ...... Int namedClassesSize = (isPreoptimized()? Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; Gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks"); } // Discover classes. Fix up unresolved future classes. Mark bundle classes. bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { ...... classref_t const *classlist = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(CLS, headerIsBundle, headerIsPreoptimized); . }}... // omit some code}Copy the code

Through the _read_images process, we finally look at the loading class information, get the list of classes from it, start reading the class information, and look at the function definition of readClass

readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->nonlazyMangledName(); . cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName ! = nullptr) { if (Class newCls = popFutureNamedClass(mangledName)) { ...... class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); // Manually set address-discriminated ptrauthed fields // so that newCls gets the correct signatures. newCls->setSuperclass(cls->getSuperclass()); newCls->initIsa(cls->getIsa()); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->getName()); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } } if (headerIsPreoptimized && ! replacing) { ...... } else {if (mangledName) {// Some Swift generic classes can lazily generate their names addNamedClass(cls, mangledName, replacing); } else { Class meta = cls->ISA(); const class_ro_t *metaRO = meta->bits.safe_ro(); . } addClassTableEntry(cls); }... return cls; }Copy the code

We can see from the readClass code that the class reads ro and assigns to RW, but does this actually happen? To verify this, use LLDB debugging. First, we need to add some code to readClass to verify that the incoming class is the object we are studying, for example LGPerson.

const char *mangledName = cls->nonlazyMangledName(); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {// Print printf("% s-atom: to study: - %s\n",__func__,mangledName); }Copy the code

inmangledNameAdd the above code, and then make a breakpoint to start debugging.According to the above running results,roandrwNot inreadClassI’m going to implement it here, and I’m just going to add tables here. thusreadClassThe main operations are as follows:

  • readclassTotal table andmach-otheclassTable comparison
  • Associate the class name with the address of the class

From this point of view it is not the rW and RO flow of the class, then we will trace the related flow from LLDB debugging, continue the above study object code in the related class loading function of the _readImage method above, and break the custom print code.

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_t const *classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (! cls) continue; / / -- -- -- -- -- -- -- -- -- -- - custom class began to -- -- -- -- -- -- -- -- -- -- -- -- -- -- const char * mangledName = CLS - > nonlazyMangledName (); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {printf("%s Realize non-lazy class-atom: To investigate: - %s\n",__func__,mangledName); } / / -- -- -- -- -- -- -- -- -- -- - end of the custom class -- -- -- -- -- -- -- -- -- -- -- -- -- -- 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 } realizeClassWithoutSwift(cls, nil); } } ts.log("IMAGE TIMES: realize non-lazy classes"); // Realize newly-resolved future classes, in case CF manipulates them if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { Class cls = resolvedFutureClasses[i]; / / -- -- -- -- -- -- -- -- -- -- - custom class began to -- -- -- -- -- -- -- -- -- -- -- -- -- -- const char * mangledName = CLS - > nonlazyMangledName (); const char *LGPersonName = "LGPerson"; If (STRCMP (mangledName, LGPersonName) == 0) {// Print printf("% s-resolvedFutureclasses-atom: - %s\n",__func__,mangledName); } / / -- -- -- -- -- -- -- -- -- -- - end of the custom class -- -- -- -- -- -- -- -- -- -- -- -- -- -- the if (CLS - > isSwiftStable ()) {_objc_fatal (" Swift, the class is not allowed to be future"); } realizeClassWithoutSwift(cls, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); }}Copy the code

Run it again and find that the current run is not broken. Doesn’t the class load process go here? According to the question we found a paragraph above the note.

// Realize non-lazy classes (for +load methods and static instances)
Copy the code

Classes that are not lazily loaded are executed+loadThe method is going to go here, and then we’re going to customizeLGPersonIn the class andloadMethod, run again.This break point comes to our judgment condition, and we go down step by step, and we get torealizeClassWithoutSwiftDelta function, that’s what we’re going to focus on.

realizeClassWithoutSwift

/*********************************************************************** * realizeClassWithoutSwift * Performs first-time initialization on class cls, * including allocating its read-write data. * Does not perform any Swift-side initialization. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; . 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 {// here is a copy from ro to rw, class set rw rw = objc::zalloc<class_rw_t>(); rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); }... supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // set class inheritance and initialize ISA CLS ->setSuperclass(supercls); cls->initClassIsa(metacls); . // Attach categories methodizeClass(cls, previously); return cls; }Copy the code

RealizeClassWithoutSwift mainly does the following:

  • frommach-oReading RO data
  • theroMake a copy of your datarw
  • Set up theclassThe inheritance chain
  • Initialize theISAWalk a
  • The method that calls the class writes the function

According to therealizeClassWithoutSwifttherwandroOperation code, we run the actual project to verify.throughLLDBDebugging can be printed hereroandrwInformation verified inrealizeClassWithoutSwiftMethods in theroandrwAt the bottom of the current function we can see the callmethodizeClassFunction, then take a look at the source of this function.

methodizeClass

/*********************************************************************** * methodizeClass * Fixes up cls's method list, protocol list, and property list. * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); . Method_list_t *list = ro->baseMethods();  If (list) {// Write the method name to methodLists and sort prepareMethodLists(CLS, & List, 1, YES, isBundleClass(CLS), nullptr); if (rwe) rwe->methods.attachLists(&list, 1); }... }Copy the code

MethodizeClass mainly contains the following points:

  • The name of the method list that reads the class
  • The method name is written to methodLists and sorted
  • Access to rwe
  • Get protocol list
  • Load classification

MethodizeClass calls prepareMethodLists. Here is the code for the prepareMethodLists function.

static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle, const char *why) { ...... for (int i = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; ASSERT(mlist); // Fixup selectors if necessary if (! Mlist ->isFixedUp()) {fixupMethodList(mlist, methodsFromBundle, true/*sort*/); }}... }Copy the code

Source code implementation of fixupMethodList methods

static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(! mlist->isFixedUp()); // fixme lock less in attachMethodLists ? // dyld3 may have already uniqued, but not sorted, the list if (! mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name()); meth.setName(sel_registerNameNoLock(name, bundleCopy)); } } // Sort by selector address. // Don't try to sort small lists, as they're immutable. // Don't try to sort big lists of nonstandard size, as stable_sort // won't copy the entries properly. if (sort && ! mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } // Mark method list as uniqued and sorted. // Can't mark small lists, since they're immutable. if (! mlist->isSmallList()) { mlist->setFixedUp(); }}Copy the code

We are infixupMethodListMethod to insert a printed piece of code, and then run to see the custom class information.See the custom class hereLGPersonIn the afterfixupMethodListMethod is executed, resulting in a list of methods whose addresses are sorted.

conclusion

According to the above source code and the analysis of the running results, the loading process of the class roughly has the following:

  1. By calling thereadImagesRead the image file of the class
  2. readClassRead the name of the class and associate it with the class
  3. realizeClassWithoutSwiftTo deal withsuperclass,isaRead,roData, and giverwThe assignment
  4. methodizeClassAccess to theroIn themethodlist, writes the method name and sorts the method list

Wrote last

Lazy and non-lazy loading, RWE, and the methodizeClass method are also discussed in this article, which will be discussed later for space reasons.