• A directory of runtime articles I have written myself
  • Objc source addresses that are runnable and constantly annotated.

What is a class?

In object-oriented programming, a class is a construct of object-oriented computer programming language. It is a blueprint for creating objects and describes the common properties and methods of the objects created. For object-C, classes include attributes and methods as well as protocols.

2. What is class loading

In the following content, if you do not understand the App startup process, it is suggested to look at the App startup process dyLD analysis.

When you open the Mach-O file, you can see that the address of the class is determined at compile time. It is just a relative address that needs to be corrected by ASLR at run time to get the correct memory address.

The loading of a class is to add the corrected class address to the memory, and the address of the class and the name of the class one by one correspondence, the attributes, protocols, methods in the class are read and saved in the hash table, convenient for the next call.

The class calls back _dyLD_OBJC_notifY_register (& MAP_images, load_images, unmap_image) when the App starts. The object of this paper is how these are implemented by Apple.

  • map_images: Map the address of the class, attributes of the class and classification, and protocols into the hash table.
  • load_images: mainly calls classes and classificationsloadMethods.

3, map_images

After map_images -> map_images_NOLock -> _read_images source view, the core code is mainly in _read_images.

The map_images_NOLock source code calculates the count in mach_header and passes it to the _read_images function.

The main source code is as follows:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

   
    static bool doneOnce;

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if(! doneOnce) { doneOnce = YES;// Determine whether to disable TaggedPointers
        if (DisableTaggedPointers) {
            disableTaggedPointers(a); }initializeTaggedPointerObfuscator(a);// Determine the number of open Spaces according to the total number of resources
        int namedClassesSize = 
            (isPreoptimized()? unoptimizedTotalClasses : totalClasses) *4 / 3;
        
        // create a hash table that holds the class
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); 
    }

    // Method load fixes
    static size_t UnfixedSelectors;
    {
      
        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; } } } ts.log("IMAGE TIMES: fix up selector references");
    
    
    // Read the list of classes from Mach-o
    for (EACH_HEADER) {
       
        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        bool headerIsBundle = hi->isBundle(a);bool headerIsPreoptimized = hi->hasPreoptimizedClasses(a);for (i = 0; i < count; i++) {
            // Get the system classes and the classes you created
            Class cls = (Class)classlist[i];
            
            // Insert the class into the gDB_objC_realized_classes table with the corresponding name
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if(newCls ! = cls && newCls) {// ...
                
                // If the current class is not the same as the original class, it needs to be reinitialized
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");

    // Fix up remapped classes
    // Fix the remapped class
    
    // ...

    ts.log("IMAGE TIMES: remap classes");

    // Load the protocol
    for (EACH_HEADER) {
       // ...
       
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

    // Fix up @protocol references
    // Fix the @protocol reference
    
    // ...

    ts.log("IMAGE TIMES: fix up @protocol references");

  
    // Find the category. You can only do this after completing the initial category attachment.
    // For categories that occur at startup, it is found to be delayed until the first load_images call after the call to _dyLD_OBJC_notify_register is complete.
    // RDAR: // question / 53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");


    // Load the non-lazy-loaded class used to implement the +load method 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;

            addClassTableEntry(cls);

            / / implementation class
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    // Future class loading
    
    // ...

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

    if (DebugNonFragileIvars) {
        realizeAllClasses(a); }// ...

#undef EACH_HEADER
}
Copy the code

Through the analysis of the above code, it is found that the main process of MAP_images mainly does the following:

  • Load all classes togdb_objc_realized_classesIn the table.
  • Remap all classes;
  • Register all SEL tonamedSelectorsIn the table.
  • Fixed function pointer legacy;
  • willProtocolAre added to theProtocol_mapIn the table.
  • For allProtocolDo remapping;
  • For classes that have been initialized, classes that have not been initialized are deferred toload_images;
  • Initialize all non-lazy-loaded classes, yesroSort, including all the parents and metaclasses of the currently non-lazy-loaded class;
  • Handles all non-lazy-loaded classesCategory, includingClassMeta Class.

4, readClass

If for a class currently read from Mach-O, readClass simply reads the class name and inserts the address and name of the class into the gDB_objC_realized_classes table, but for popFutureNamedClass, Here are the ones who can read ro and write Rw.

The source code is as follows:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName(a);// ...
    
    Class replacing = nil;
    if(mangledName ! =nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // popFutureNamedClass reads ro and writes RW, but normal reads from Mach-o do not
           
            class_rw_t *rw = newCls->data(a);const class_ro_t *old_ro = rw->ro(a);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 
            // Some fast generic classes can lazily generate their names
            
            // Associate the class with the class name
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA(a);const class_ro_t *metaRO = meta->bits.safe_ro(a); }// Insert the table once, to prevent missing inserts, inserts will not be repeated inserts
        addClassTableEntry(cls);
    }
    return cls;
}

Copy the code

5, realizeClassWithoutSwift

Classes that implement non-lazy loading, not Swift, why not just implement non-lazy loading classes?

In development, some properties or objects that may not be initialized are loaded lazily to save memory, and then initialized when the property or object is used for the first time. The same is true for classes.

The difference between a lazy-loaded class and a non-lazy-loaded class is whether the +(void)load {} method is implemented.

The source code is as follows (annotated) :

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked(a);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(a);auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data(a); ro = cls->data() - >ro(a);ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        
        // Create space
        rw = objc::zalloc<class_rw_t> ();// give ro to rw
        rw->set_ro(ro);
        
        // The class has been implemented but has not yet completed it
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        
        // Assign a value to data.bits of the class
        cls->setData(rw);
    }

    // Initialize to null or pre-optimize in camouflage
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise(a);#if FAST_CACHE_META
    // Formalize RO_META to avoid indirection
    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
    
    // Select the index for the class.
    cls->chooseClassArrayIndex(a);// Initialize the parent class
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    
    // Initialize the metaclass
    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.
        
        // The metaclass does not need any features from non-pointer ISA
        cls->setInstancesRequireRawIsa(a); }else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa(a);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) {
            // Mark this class and all its subclasses as requiring the raw ISA pointer
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); }}// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    
    // Set the parent class
    cls->setSuperclass(supercls);
    
    // Initialize isa
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // Reconcile the offset of the instance variable.
    
    // This may reallocate class_ro_t, updating our ro variable.
    // You can reassign class_ro_t and update the ro variable.
    
    if(supercls && ! isMeta)reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    // If fastInstanceSize is not already set, set it.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor(a);if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    
    // Propagate the associated object disable 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
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    // Load the classification
    methodizeClass(cls, previously);

    return cls;
}
Copy the code

Through the analysis of the above code, it is found that the main process of realizeClassWithoutSwift mainly does the following things:

  • class_rw_t *rwOpen up space;
  • willclass_ro_t *roAssigned toclass_rw_t *rwIn thero;
  • Marks the state of the current class, which has started implementation but has not completed it.
  • willrwclsdata.bits;
  • If a subclass is not lazily loaded, then its metaclass, parent, and parent metaclass all need to be initialized recursively.
  • Bidirectional link between subclass and parent class;
  • Metaclass initializationisadisablenonpointerIsa;
  • fromrosuperclassIntegration related object disable flags;
  • Categorization and loading.

6, methodizeClass

1, class_rw_ext_t * rwe

Before WWDC20, Apple used the runtime API to modify the RW of each class. When each class is initialized, it copies the list of class_ro_t *ro methods, protocols, and attributes into the RW. Below (from this blog post) :

But Apple’s data shows that 90% of classes in most apps do not operate on class_rw_t *rw.

So after WWDC20, Apple optimized and split class_rw_t, and applied a separate class_rw_ext_t *rwe for the method list, protocol, and attribute, and initialized it only when it was in use to reduce memory usage and optimize memory. After optimization, the picture is as follows:

In other words, if there is no dynamic addition to the class from WWDC20, the value is directly fetched from class_ro_t *ro. With this explanation, some of the following parts are easier to understand.

Before I saw objC-752, I remember copying ro to RW. After looking at RWE for a long time, I thought I was wrong. After Google, I found that it was changed in 2020.

2, methodizeClass

In this case, we did not dynamically add methods to our classes. Class_rw_ext_t *rwe is NULL. Only when RWE has a value is it copied from ro’s list of methods, protocols, attributes, and so on to RWE.

The source code is as follows:

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

3, prepareMethodLists

Determines whether the methods in the class need sorting, which is done with fixupMethodList.

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    // ...

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    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

4, fixupMethodList

Sort the address of the selector.

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.
    // Sort the address of the selector.
    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

7. Classification loading

1. AttachToClass method

Add the list of methods, protocols, and properties from the class to the main class.

This method is only available when a class is added dynamically.

void attachToClass(Class cls, Class previously, int flags)
{
    runtimeLock.assertLocked();
    ASSERT((flags & ATTACH_CLASS) ||
           (flags & ATTACH_METACLASS) ||
           (flags & ATTACH_CLASS_AND_METACLASS));
           
    auto &map = get();
 
    auto it = map.find(previously);

    if(it ! =map.end()) {
        category_list &list = it->second;
        
        // Determine the metaclass
        if (flags & ATTACH_CLASS_AND_METACLASS) {
            int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
            // Add instance methods
            attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
            // Append class methods
            attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
        } else {
            // Append instance methods
            attachCategories(cls, list.array(), list.count(), flags);
        }
        map.erase(it); }}Copy the code

2, attachCategories

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    // ...
    
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    
    // Initialize rWE
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                
                // rwe calls attachLists to add method
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        // Add attributes
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        // Add a protocol
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; }}if (mcount > 0) {
        / / sorting
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        // Perform memory translation
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                return! c->cache.isConstantOptimizedCache(); }); } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); }Copy the code

3, attachLists

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    
    if (hasArray()) {
        // many lists -> many lists
        
        // Save the size of the previous array
        uint32_t oldCount = array()->count;
       
        // Calculate the size of the new array
        uint32_t newCount = oldCount + addedCount;
        
        // create a new space for newCount
        array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
        
        // reassign count
        newArray->count = newCount;
        array()->count = newCount;
        
        for (int i = oldCount - 1; i >= 0; i--)
            // Place the methods in the old array behind the new array in order
            newArray->lists[i + addedCount] = array()->lists[i];
            
        for (unsigned i = 0; i < addedCount; i++)
            // Put the methods in the new array in front
            newArray->lists[i] = addedLists[i];
        
        // Free the old array
        free(array());
        
        // Set the new array
        setArray(newArray);
        
        validate();
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
        validate();
    }
    else {
        // 1 list -> many lists
        
        // If there is no original array, then the array is classified array
        
        Ptr<List> oldList = list;
        
        //
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        
        // Set the array for next judgment
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        
        array()->count = newCount;
        
        if (oldList) 
            array()->lists[addedCount] = oldList;
        
        for (unsigned i = 0; i < addedCount; i++)
            array()->lists[i] = addedLists[i]; validate(); }}Copy the code

In the attachLists method, it is important to see that when adding classification methods to the main class, the method list in the old array is moved back and the new method list is first.

According to the compilation process, if a main class has 10 classes, then the method of the 10th class with the same name will be executed first, and subsequent classes will not be executed. That’s because when you look up a method, once it’s found, it’s returned, rather than the last classification method overwriting all the previous methods with the same name.

8, the use of classes and categories

type The classification implements load Class load is not implemented
The main class implements load Load_images loads the classification The classification is compiled into the main class at compile time
The main class does not implement load Forces the main class to initialize first, load_images loads the classification The classification is compiled into the main class at compile time

1. Non-lazy loading classes + non-lazy loading classes

This is the most normal process, but one that rarely occurs in development.

The source code call process is as follows:

map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> AttachCategories – > attachLists.

Lazy loading class + non-lazy loading classification

This process is often used when dealing with method_swizzing in development, because classes are not lazily loaded, so they force the main class to load early and follow the previous process.

3, non-lazy loading class + lazy loading classification

The main class is implemented in read_images via realizeClassWithoutSwift, and the classification methods are compiled directly into the main class at compile time.

4, lazy loading class + lazy loading classification

LookUpImpOrForward is implemented when the main class is first called, and the classified methods are compiled directly into the main class at compile time.

9 load_images.

If the category implements the load method, loadAllCategories is called to load the methods in the category into the main class.

And then we call the main class load and then we call the class load.

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        
        Load is called if the class implements load, which loads the methods in the class into the main class
        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 all classes that implement the load method
    call_load_methods();
}

Copy the code

Here's a question: If someone asks that the main class and the classification implement methods of the same name, who executes them?

A: If the method with the same name is load, the main class is executed first and then the classification is executed. If other methods, in compile order, implement the same name of the last class, because the newly added class method is first in the current class, when the method finds the first one, it doesn’t look any further.

The load method executes the main class first and then the class for the following reasons:

Above saw load_images implementation methods, only didCallDyldNotifyRegister to true when the execution loadAllCategories () method, But didCallDyldNotifyRegister is set to true in objc_init _dyld_objc_notify_register (& map_images, load_images, unmap_image); Method, so the main class method is executed first.

void _objc_init(void)
{
   // ...

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

10 and summarize

This is the end of class loading. After studying class loading, here are some tips:

  • The main class and classification can not implement load method can not implement load method, affect the startup time;

  • Classification can not implement load method, load method is not implemented, affecting the startup time;

  • If the inheritance chain is long, it is best if the subclass does not implement load, because the subclass implements load, and all classes in the current inheritance chain will be implemented.

Thank you for reading, if it is helpful to you, I hope to give a thumbs-up, if there is a mistake, I hope the big guy pointed out, study together, progress together, thank you.