In the last two chapters, we have seen what happens between compiling an App and calling main, and we have seen that _objc_init, when loading an image file, is unregistered in the dyLD dynamic linker, through which the two communicate. However, after dyLD loads relevant image files, how these image files are loaded into memory and how they are stored in memory is the core of this chapter.

Related articles Portal:

☞iOS Low-level Learning – From Compilation to Startup (PART 2)

☞iOS Basic Learning – Class Past Life (I)

☞iOS low-level learning – primary classes, protocols, extensions

We know that dyLD’s main process is to link dynamic library and image file, so how does objC image file itself read into memory, we read from the source code

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

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

Through the source code, we can basically draw the following conclusions:

  • _objc_initbylibSystemLibrary calls
  • In this method bootstrap image is loaded into memory

How are objC related classes loaded

The preparatory work

Before the image is loaded, Objc does a series of preparatory work, which we will analyze step by step, as shown below:

environ_init

Export OBJC_HELP=1 to print environment variables for debugging. Xcode can be set to print the desired environment variables. See OBJC_HELP for a reference

  • OBJC_DISABLE_NONPOINTER_ISAThis can be setnon-pointerISA, the value of ISA does not need andmaskConduct and operate, direct to
  • OBJC_PRINT_LOAD_METHODSThis can print classes and categoriesloadMethod for us to start optimization is very helpful.

tls_init

This function is about thread Key bindings, such as thread data destructors

static_init

As you can see from the comments, this function mainly does the following

  • Run the C++ static constructor
  • Before DYLD calls our static constructor,libcWill be called_objc_init(), so it must be called before
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for(size_t i = 0; i < count; i++) { inits[i](); }}Copy the code

lock_init

Is an empty implementation, indicating that objc uses the C++ locking mechanism

exception_init

Initialize libobJC’s exception handling system to monitor crashes, such as unimplemented methods

_dyld_objc_notify_register

From the previous chapter, we learned that this method is a dyLD registration callback function that allows DyLD to link and load images

  • This function is only available to ObjC at runtime
  • Register handlers to be called in mapping and unmapping and initializing objC images
  • Dyld will use includeobjc_image_infoArray of image files to call back tomappedfunction

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
Copy the code

map_images

This method is the main method that triggers when an image is loaded into memory, so let’s explore how this method loads data, classes, categories, methods, and so on into memory.

/***********************************************************************
* map_images
* Process the given images which are being mapped inby 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

If hCount is the number of image files, the _read_images function is called to load the image file. So load memory must be here

_read_imagesparsing

Since the code is quite long, we will make a general summary first and then conduct research step by step. The basic treatment is as follows:

  • Load all classes into the table for the first time

    Gdb_objc_realized_classes is a table for all classes – both implemented and unimplemented

    AllocatedClasses contain tables of all classes (metaclasses) allocated using objc_allocateClassPair

  • Remap all classes

  • Register all SEL’s in the namedSelector table

  • Fixed old objc_msgSend_fixup call causing some messages not to be processed

  • Adds all protocols to the PROTOCOL_map table

  • Remap all protocols to get references

  • Initialize all non-lazy-loaded classes for RW, RO, etc

  • Iterate over the lazy-loaded classes that have been marked and initialize them accordingly

  • Handles all categories, including classes and metaclasses

  • Initialize all uninitialized classes

Below we mainly on the class load to carry on the key analysis

doneOnce

The variable doneOnce indicates that this operation is performed only once, and since it is a table creation operation, only one creation is required. The main code is as follows

    if (!doneOnce) {
        doneOnce = YES; . initializeTaggedPointerObfuscator(); // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable'Int namedClassesSize = (isPreoptimized()? unoptimizedTotalClasses : totalClasses) * 4 / 3; //✅ create a table containing all the classes and metaclass gdb_objC_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); AllocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); //✅ create table allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); ts.log("IMAGE TIMES: first time tasks");
    }

Copy the code

Class remapping

The code for class remapping is as follows. Classes are iterated from the list, processed and added to the corresponding table

// Discover classes. Fix up unresolved future classes. Mark bundle classes.

    for(EACH_HEADER) {// ✅ retrieve a pointer to classref_t 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; System classes OS_dispatch_queue_concurrent, OS_xpc_object, NSRunloop, etc., such as CF, Fundation, libDispatch, etc. Class CLS = (Class) classList [I]; / / ✅ throughreadThe Class function gets the new Class after processing, which is examined belowreadClass
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); //✅ initializes the memory required by all lazy-loaded classes - the data is not currently loaded - even the class is not initializedif(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. //✅ add lazy classes to array resolvedFutureClasses = (class *) realloc(resolvedFutureClasses, (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; }}}Copy the code

How does readClass handle classes?

We can see the following code, the CLS rW, etc., we know that the RW store class methods, etc., so is it handled here?

But when we break the point in the method, we find that there is no execution, which means that neither the class we created nor the class of the system method uses this method, so the rW data of the class is not here

From the judgment in the red box, we can see that the judgment condition is to handle a special operation for the class to be processed specifically for the future

So if you move down to the code below, you can see that the addNamedClass and addClassTableEntry functions are basically executed

if(headerIsPreoptimized && ! replacing) { // class list builtin shared cache
        // fixme strict assert does not work because of duplicates
        // assert(cls == getClass(name));
        assert(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
}
Copy the code

Look at the addNamedClass code to add classes to the underlying total hash table

/*********************************************************************** * addNamedClass * Adds name => cls to the named  non-meta class map. * Warns about duplicate class names and keeps the old mapping. * Locking: runtimeLock must be held by thecaller
**********************************************************************/
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 bein the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else{//✅ add classes to the total table 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()); }Copy the code

Look at the code for addClassTableEntry, because the current class already has an address and is initialized, so it should also be added to the allocatedClasses hash table

/*********************************************************************** * addClassTableEntry * Add a class to the table  of all classes. If addMeta istrue,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
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 inthe dynamic table already. assert(! NXHashMember(allocatedClasses, cls));if(! isKnownClass(cls)) NXHashInsert(allocatedClasses, cls);if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}
Copy the code

At this point, the initialization classes have been added to both tables

SELAdd to table

SEL code is handled as follows, which is basically a table write operation, which is written to the namedSelector table, and the class is not a table

// 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]); //✅ register SEL sels[I] = sel_registerNameNoLock(name, isBundle); }}}Copy the code

All of theProtocolAre added to theprotocol_mapIn the table

The relevant codes are as follows.

// Discover protocols. Fix up protocol refs. //✅ iterate over all protocol lists and load the protocol list into the protocol hash tablefor (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol; //✅ CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to the Protocol Class CLS = (Class)&OBJC_CLASS_$_Protocol; assert(cls); NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->isPreoptimized(); bool isBundle = hi->isBundle(); //✅ reads and initializes Protocol Protocol_t **protolist = _getObjc2ProtocolList(hi, &count);for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }}Copy the code

Initialize all non-lazy-loaded classes for RW, RO, etc

The loading of a class can be divided into lazy and non-lazy classes

The main difference between the two is whether the load method is implemented

  • A non-lazy-loaded class if the load method is implemented

  • If it is not implemented, it is a lazy loaded class

Non-lazy loading class loading process

Since the non-lazy loading class implements load method, and DYLD will call various load methods when loading image link, it is necessary to load related classes when the image is added to memory

    // Realize non-lazy classes (for+load methods and static instances) //✅ implement non-lazy loading classes, for load methods and static instance variablesfor(EACH_HEADER) {//✅ to get a list of non-lazy-loaded classes classref_t * classList = _getObjc2NonlazyClassList(hi, &count);for(i = 0; i < count; I ++) {//✅ map from the mirror list Class CLS = remapClass(classList [I]); //printf("non-lazy Class:%s\n",cls->mangledName());
            if(! cls)continue;

            // hack for class __ARCLite__, which did not 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//✅ insert addClassTableEntry(CLS) into allocatedClasses;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 not disallow all Swift classes because of // classes like Swift.__emptyarraystorage} //✅ Implement all non-lazy-loaded classes (instantiate some information of the class object, such as RW) realizeClassWithoutSwift(CLS); }}Copy the code

View realizeClassWithoutSwift code

Ro stands for readonly, which is assigned at compile time, but rW is not assigned at this time, so this step is mainly to initialize the RW

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

We can see from the following code that we’re dealing with superclass and ISA, and we’re doing recursive operations based on the inheritance chain of the class, and we know that the class is nil, which is the parent class of NSObject. Thus complete the improvement of the class inheritance link

if(! cls)returnnil; . supercls = realizeClassWithoutSwift(remapClass(cls->superclass)); metacls = realizeClassWithoutSwift(remapClass(cls->ISA())); cls->superclass = supercls; cls->initClassIsa(metacls);Copy the code

But after ISA found the root metaclass, the root metaclass isa is pointing to their own, must not give nil, if not processed, will form infinite recursion, through viewing remapClass source code, we can know, remapClass is mainly on the class in the table to find the operation, if the table has the class, then return a null value, If not, the current class is returned, ensuring that the class is loaded only once and ending the recursion

/***********************************************************************
* remapClass
* Returns the live class pointer for cls, which may be pointing to 
* a class struct that has been reallocated.
* Returns nil if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class remapClass(Class cls)
{
    runtimeLock.assertLocked();

    Class c2;

    if(! cls)return nil;

    NXMapTable *map = remappedClasses(NO);
    if(! map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {return cls;
    } else {
        returnc2; }}Copy the code

MethodizeClass (methodizeClass, methodizeClass)

static Class realizeClassWithoutSwift(Class cls)
{
    ...
    // Attach categories
    methodizeClass(cls);
    return cls;
}

Copy the code

In the implementation of methodizeClass, we find that the assignment of the newly initialized RW is to take the relevant data from ro and assign it directly to RW

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

So how do attachLists insert data

According to the code, we can find that, mainly by offsetting oldList back to the position of addedCount, and then inserting new addedLists as a whole in the front of the table, so as to achieve the classification method to override the method of the same name of the class, so the classification method will be called before the original method, and not overwrite

    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; / / 4setArray((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

The main process for loading a non-lazy-loading class is as follows

Read_images creates a table gdb_objC_realized_classes for the global class and a table allocatedClasses for the allocatedClasses class. Then, SEL and Protocol are also mapped to the corresponding table in memory, and the class is not the same table, and in the process of non-lazy loading class, ro is assigned by realizeClassWithoutSwift, and rW is initialized. MethodizeClass was then used to assign values to RW to complete data loading

Lazy loading class loading process

Since lazy-loaded classes do not implement the load method, they do not need to be loaded into memory at startup. When do lazy-loaded classes get loaded into memory?

From the above exploration of non-lazily loaded classes, we know that non-lazily loaded classes find and load their parent classes, so if a lazily loaded class is a parent class, it must be loaded recursively

Another guess is that if the class calls a method and sends a message, the class must have data from the RW, meaning it must have been loaded. We can explore according to the source code of message forwarding

Looking for lookUpImpOrForward, we find one! CLS ->isRealized(

if(! cls->isRealized()) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again }Copy the code

By breaking point, we can find that when no call before class, first alloc, will enter this judgment, further, check realizeClassMaybeSwiftMaybeRelock method implementation

/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure forthe class. * Locking: * runtimeLock must be held on entry * runtimeLock may be dropped during execution * ... AndUnlockfunction leaves runtimeLock unlocked on exit
*   ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
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 wrongin 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

We found that this method is to distinguish the Swift, is OC, main realizeClassWithoutSwift running method, this method, in the above the lazy loading class talked about in the process, main is initialized to the relevant properties of a class

At this point, a lazy class has been loaded, the summary is that a lazy class is loaded the first time it is called, not loaded into memory at startup

At this point, the assignment loading of all the attributes required by a class is complete

Deal with allCategory, including classes and metaclasses

For some basic knowledge of categorization, see the following article 👇

Portal ☞iOS low-level learning – A primer on categories, protocols, extensions

The loading of a class is similar to the loading of a class in that it is also necessary to distinguish between calls to the load method

  • Lazy loading – determined at compile time
  • Non-lazy-loaded – runtime determination

Non-lazy load classification

The non-lazy load category implements the load method, which we can analyze together with class loading

1. Lazy loading classes

Through the exploration of the above, we know that lazy loading class is used for the first time when loaded into memory, then due to the classification of the classification is a lazy loading, he also needs to join the class information in the ro and rw, so this time will be loaded in advance, rather than wait until when sending a message to initialize, specific code is as follows

Category_t ** catList = _getObjc2CategoryList(hi, &count);Copy the code
// ✅ judgments are instance methodsif(cat->instanceMethods || cat->protocols || cat->instanceProperties) { ... / / ✅ add to the list of the classification of the unbounded addUnattachedCategoryForClass (cat, CLS, hi); }... // ✅ determine the class methodif(cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { ... addUnattachedCategoryForClass(cat, cls, hi); } // ✅ is not loaded because it is lazyif (cls->isRealized()) {
      remethodizeClass(cls);
      classExists = YES;    
  }
Copy the code

After the _read_image method is finished, all non-lazy loading classes and classes that implement load method have been added to the table, but non-lazy loading classes are not bound to classes at this time. Therefore, when the load_image function calls load method, preparations are made and relevant classes are loaded

void load_images(const char *path __unused, const struct mach_header *mh) { ... // Discover load methods { mutex_locker_t lock2(runtimeLock); // ✅ prepares a call to the load method prepare_load_methods((const headerType *)mh); }... }Copy the code
void prepare_load_methods(const headerType *mhdr) { ... / / ✅ map_images finished category_t * * 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"); } // ✅ load the corresponding class and add the classification to the load List realizeClassWithoutSwift(CLS); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}Copy the code

2. Non-lazy-loaded classes

Non-lazily loaded classes and classes of non-lazily loaded classes are relatively simple. The non-lazily loaded classes have been inserted into the table in _read_image and loaded. Therefore, the judgment method remethodizeClass has been executed in CLS ->isRealized()

We can see that remethodizeClass mainly performs the attachCategories function, which inserts the categories into the RW of the class

Since classified methods are executed last, if a method with the same name is inserted, the classified method is found first, but the original method in the class is not overwritten

Lazy load classification

Since categories are lazily loaded, the _getObjc2CategoryList does not contain this method, so there is no way to retrieve it in the _read_image method

Lazy load classes are written directly into the class’s ro at compile time, regardless of whether the class is lazily loaded or not. When it is loaded, the ro value is directly loaded and assigned to the corresponding RW

At this point, the loading of the associated images is complete

load_image

Load_image () load_image () load_image () load_image () load_image () load_image

  • To prepareloadThe data required for the method call
  • callloadmethods
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // 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 // ✅ Prepare data needed for load method calls {mutex_locker_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } // Call +load methods(without runtimelock-re-entrant) // ✅ call_load_methods(); }Copy the code

prepare_load_methods

This method is used to prepare the load table of classes and categories, mainly by creating a list of non-lazy load classes and non-lazy load categories, the specific implementation is as follows

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // ✅ iterates through non-lazy-loaded classes
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **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");
        }
        // ✅ loads related classes
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        // ✅ adds categories to the list of non-lazy loading categoriesadd_category_to_loadable_list(cat); }}Copy the code

When a non-lazy-loaded class is added to the list, the parent class is added recursively

static void schedule_class_load(Class cls)
{
    if(! cls)return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    // ✅ add the parent class recursively
    schedule_class_load(cls->superclass);
    // ✅ Add the class to the list
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

Copy the code

The loadable_class stored in the list has a structure of 16 bytes. Therefore, the existing space is 2 times +16 bytes, and the corresponding CLS and method are assigned.

The logic for adding categories to the list is basically the same, except that the stored list and model have different names

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

Copy the code
/*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if(! method)return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
         // ✅ apply for space
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    / / ✅ model assignment
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
Copy the code

call_load_methods

Call_load_methods is a function that actually calls the load method. The main operations are as follows

  • The automatic release pool is placed when called
  • Loop through the list, calling the correspondingloadmethods
  • Automatically release the pool after the call out of the stack, ensuring that the call once
  • The load method of a class is called before the load method of a class, based on the order in which it is called
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;
    
    // ✅ automatic stack release pool
    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // ✅ calls the load method of the class
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // ✅ loop calls the class load method once
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    // ✅ automatic release pool
    objc_autoreleasePoolPop(pool);

    loading = NO;
}
Copy the code

Loads are called in call_class_loads, the main operation is to traverse the list of non-lazy-loading classes, get IMP directly, force load_method_t, and call function implementation directly without going through the message sending process

/*********************************************************************** * call_class_loads * Call all pending class +load methods. * If new classes become loadable, +load is NOT called for them. * * Called only by call_load_methods(). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // ✅ iterate through the list, get IMP and strong
        load_method_t load_method = (load_method_t)classes[i].method;
        if(! cls)continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        // ✅ call the function directly
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
Copy the code

The process of calling categories is similar to that of calling classes, except that the data is fetched from the loadable_categories list and freed after the call, guaranteeing that it is called once

static bool call_category_loads(void)
{
    ...
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if(! cat)continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", cls->nameForLogging(), _category_getName(cat)); } (*load_method)(cls, SEL_load); cats[i].cat = nil; }}... // Compact detached list (order-preserving)shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift+ +; } } used -=shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if(used == allocated) { allocated = allocated*2 + 16; cats = (struct loadable_category *) realloc(cats, allocated * sizeof(struct loadable_category)); } cats[used++] = loadable_categories[i]; }}Copy the code

At this point, the load calls for the classes and categories are also complete

initialize

The initialize method will be called when the message is sent, and the lazy load method will be called when the message is sent

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{...if(initialize && ! cls->isInitialized()) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock);// runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172}... }// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

Copy the code
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{... initializeNonMetaClass (nonmeta); ...}Copy the code

We found the initializeNonMetaClass method and found that the method was still the same, recursively calling the parent class Initialize and then the actual call function callInitialize below

/*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void initializeNonMetaClass(Class cls)
{...// ✅ recursively calls the parent class
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    ...
    {
            // ✅ calls the Initialize method
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", pthread_self(), cls->nameForLogging()); }}... }Copy the code

CallInitialize is a familiar method for sending a normal message

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
Copy the code

The Initialize method is now called

conclusion

This chapter mainly describes the steps of loading classes, methods, protocols and classes in OBJC into memory and the call process of load method

The main process is as follows:

  1. Prepare related environment variables, threads, locks, exception listeners anddyldPre-conditions such as registration
  2. throughmap_imagesIn the_read_imageTo deal with classes, methods, protocols, classes, etc
  3. Class creates a master table and an initialized table to manage
  4. Non-lazily loaded classes in_read_imageFromroTo read the corresponding data, assign the valuerw
  5. Lazy-loaded classes do nothing about class loading until they are called for the first time
  6. The non-lazy load classification is determined at run time and responds if no class is loaded
  7. Lazy load classification is determined at compile time, directly inroIs assigned to a class
  8. load_imageCalls mainly to classes and classificationsloadMethod, and stored in different lists
  9. According to the call order, the classloadClassification of thanFirst call. Other methods, due to classification after insertionrw, so classes are called before class methods, butThere is no coverage
  10. initializeCalled when a message is sent