Here we will pick up where we left offIOS Basic Exploration — Class Loading (part 1)Keep exploring.

For a quick reminder, we start from _objc_init -> _dyLD_objc_notify_register -> map_images -> _read_images; So I trace it all the way to _read_images. We looked at 10 steps to load the image.

In the 10 steps, there are some confusion of information processing, class loading, protocol, classification and so on information processing. At this point our focus is on class loading. So the focus is on step 9.

The key function here is realizeClassWithoutSwift. So today we’re going to focus on exploring the logic of this function.

Before we know what we need to do, we need to supplement the knowledge points, because there are some knowledge points in realizeClassWithoutSwift that we need to know in advance.

  • ro: belong toclean memory, i.e.,
    Memory space determined at compile time read-only \color{red}{memory space determined at compile time — read-only}
    ; Memory does not change after loading.
  • rw: belong todirty memory, i.e.,
    Memory generated at run time Can read but write \color{red}{memory generated at runtime — readable and writable}
    ; You can add attributes, methods, and so on to a class, which is memory that can be changed at run time.
  • rwe: the equivalent ofAdditional information about the classBecause very few classes actually change their content during use, it is necessary to avoid wasting resourcesrwe.

If the runtime needs to dynamically add methods, protocols, and so on to a class, it creates an RWE and attaches ro data to the RWE first. Rwe data is preferentially returned when read; If Rwe is not initialized, return ro. (1) There are extensions, taken from rwe; ② No extension, from ro.

Rw contains ro,rwe. The purpose is to make the dirty memeory take up less memory and extract the variable parts of the RW as rWE.

The more clean memeory the better and the less dirty memory the better. The underlying mechanism of iOS is virtual memory. If the memory is insufficient, part of the memory will be reclaimed. Load it from disk for later use.

Clean Memeory is memory that can be reloaded from disk, such as dynamic libraries, Mach-O files, etc.

Dirty memory is run-time data that cannot be reloaded from disk; So you have to use up memory all the time.

If the system physical memory is insufficient, clean memory is reclaimed. If the dirty memeory is too large, it is recycled directly.

The purpose of designing RO, RW and RWE is to distinguish clean memory and dirty memeory in a better and more detailed way.


realizeClassWithoutSwift

Again, let’s look at the source code first:

/*********************************************************************** * realizeClassWithoutSwift * Performs Initialization on class CLS, performing initial initialization of class CLS * including allocating its read-write data. * Does not perform any swift-side initialization. Returns the real class structure for the class. Returns the real class structure of the class * Locking: RuntimeLock must be write-locked by the caller: RuntimeLock must be written by the caller to lock in * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; if (! cls) return nil; if (cls->isRealized()) { validateAlreadyRealizedClass(cls); return cls; } ASSERT(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? 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); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); } cls->cache.initializeToEmptyOrPreoptimizedInDisguise(); #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif // 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->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA if (isMeta) { // Metaclasses do not need any features from non pointer ISA // This allows for  a faspath for classes in objc_retain/objc_release. cls->setInstancesRequireRawIsa(); } else { // 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 && 0 == strcmp(ro->getName(), "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->getSuperclass() && 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->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); } } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping cls->setSuperclass(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, previously); return cls; }Copy the code

Note the official comment on this function, which gives the translation. The realizeClassWithoutSwift method is mainly used to implement classes. The official annotation states clearly that it Returns the real class structure for the class, which Returns a real class structure.

There are mainly the following steps:

  • 1, readdataData, Settingsro.rw.
  • 2, the recursionrealizeClassWithoutSwift(Recall hereIsa bitmap).
  • 3, set some information about the class, subclass, parent class and so on.
  • 4,methodizeClass, additional categories.

1, readdataData, Settingsro.rw

We have introduced it aboveroandrwRelated definitions of.


2, the recursionrealizeClassWithoutSwift

This is going to be called recursivelyrealizeClassWithoutSwift, for the current classThe parent class.The metaclassInitialize.


3, set some information about the class, subclass, parent class and so on

In theThe parent classandThe metaclassLater, theThe current classConfigure. Of course, there are recursive calls up there, so, or there areifJudge, take a lookThe current classThe type of.


4,methodizeClass, additional categories

MethodizeClass: methodizeClass: methodizeClass From the official comment, we know that this method is the method list, protocol list, and property list for modifying CLS.

/***********************************************************************
* 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();

    // 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), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->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, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#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
  • Although the official note here isAttach categoriesBut don’t think of it as just adding methods to the classification. Since there is a comment in the source code, take a closer look:

So if you look at the way that methods and properties are handled here, that includes the methods and properties of the class itself. The same is true for the protocol because it is also available in the source codebaseProtocols:

Finally, the method list, property list, and protocol list are attached to the RWE.


4.1 prepareMethodLists

As we saw above, forMethods listThe handler is not added directly to the callrwe->methods.attachLists(&list, 1);. Also called before attachingprepareMethodListsFunctions; So what does this function do? Let’s follow it through, and we find a code that looks like this. (Note: herelistThe incoming is&list)

That is, the list of methods is sorted once before being attached to the RWE. (As can be inferred by sore, note the red circle in the figure above.)


4.2 fixupMethodList

We tracked it up herefixupMethodList, presumably this is a pairMethods listA function to sort. So how exactly is that sorted? Let’s go into the function:As you can see,Methods listIs based onselecor addressTo sort it.

Also, before sorting, according toifI will do it againName (returned SEL)Settings. (Look at the code block above the red box.)

  • This is something we can verify by printing. We are infixupMethodListIn, print before and aftermethodlist:

  • Let’s make our own class and call, (note: call hereloadMethod, otherwise the system defaults to lazy loading.
  • Next, run the project (source project) and search for our custom methods in the console print (because there are some systematic prints, there will be more). , you can see that the methods are sorted by address:

Addendum: a binary lookup method is encountered during a slow lookup of a method. The basis of binary lookup is that the list of methods has been sorted.

Here we use pseudocode to simply implement the following binary search: