+ Load function analysis

Now that I have written here, let’s analyze the process of calling the +load function.

  • implementation+loadThe classification and class of functions are non-lazy loading classification and non-lazy loading class, not implemented+loadThe classes and classes of functions are lazy-loaded classes and lazy-loaded classes. Lazy-loaded classes are implemented only when we first use them.
  • +loadThe function is executed directly according to its address, unlike other OC functions throughobjc_msgSendMessage sending process to perform. Class and classification+loadThere is no “function overlay” phenomenon when the original class and its category have the same function. In the right time class and classification+loadThe class must precede the class, the parent must precede the subclass, and the first to compile the class is the first to execute. (This is the opposite of the fact that a function in a different class with the same name will “override” a function in the first compiled class.)
  • In no case should we call it manually+loadFunction, we just wait for the system call, and one+loadThe function is called globally only once (it should never exist[super load]Calls).

Revisited load_images

The load_images function is the +load function that calls the classes and categories in the specified mach_header. A comparison of objC4-779.1 shows that the load_images function in objC4-781 has one more judgment.

 if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
     loadAllCategories(a); }Copy the code

In ObjC4-781, Apple delayed loading the category data, which was previously loaded in _read_images in ObjC4-779.1. We’ve already analyzed this process, but we’ll focus on the implementation of the +load function.

/* * Load_images * Process +load in the given images which are being mapped in by dyld Class and the +load function in the class. * Locking: write-locks runtimeLock and loadMethodLock */

// C language forward declaration, these two functions will be analyzed line by line

// Check whether mach_header contains classes and classes that are not lazily loaded.
// Check whether there are (__DATA_CONST, __objc_nlclslist) and (__DATA_CONST, __objc_nlcatList) sections in the binary executable file.
// These two partitions only exist in the mach-O binary executable if the +load function exists in our custom source code. (Many system classes implement the +load function, but these two partitions do not exist and only store our custom classes and classes.)

extern bool hasLoadMethods(const headerType *mhdr);

// Prepare before calling load
extern void prepare_load_methods(const headerType *mhdr);

void load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        loadAllCategories(a); }// Return without taking locks if there are no +load methods here.
    // If there is no load function, return directly.
    if (!hasLoadMethods((const headerType *)mh)) return;

    // This is a recursive lock
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    // Find load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // Prepare the load function
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // Call +load
    call_load_methods(a); }Copy the code

hasLoadMethods

// Quick scan for +load methods that doesn't take a lock.
// The +load function is not locked for fast scanning.
bool hasLoadMethods(const headerType *mhdr)
{
    size_t count;
    
    // GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
    // Check whether the __objc_nlclslist section under the DATA segment has non-lazily loaded classes
    
    if (_getObjc2NonlazyClassList(mhdr, &count)  &&  count > 0) return true;
    
    // GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
    // Check whether the __objc_nlcatList field under the DATA segment has a non-lazy load category
    
    if (_getObjc2NonlazyCategoryList(mhdr, &count)  &&  count > 0) return true;
    
    return false;
}
Copy the code

The hasLoadMethods function is used to determine whether the MHDR parameter (of type mach_header_64 structure) contains non-lazy-loaded classes and classes (classes and classes that implement the load function). The method is to determine whether there is data in the (__DATA_CONST, __objc_nlclslist) and (__DATA_CONST, __objc_nlcatList) sections of the Mach -O binary executable files. These two partitions only exist in the Mach-O binary executable if the +load function exists in our custom source code (the system classes also implement the +load function, but they do not exist in these two partitions, which store only our custom classes and classifications).

If the OBJC_PRINT_LOAD_METHODS environment variable is enabled, the console displays the following:

objc[76876]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[76876]: LOAD: +[NSObject(NSObject) load]

objc[76876]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[76876]: LOAD: +[NSObject(NSObject) load]

objc[76876]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[76876]: LOAD: +[NSError(FPAdditions) load]

objc[76876]: LOAD: class '_DKEventQuery' scheduled for +load
objc[76876] : LOAD: +[_DKEventQuery load]
Copy the code

The (__DATA_CONST, __objc_nlclslist) and (__DATA_CONST, __objc_nlcatList) sections do not exist.

prepare_load_methods

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

    runtimeLock.assertLocked(a);// Select count from MHDR
    The load function call class precedes the class
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
        
    // The loop adds different classes of load functions to the global array of load methods, first adding the parent class's +load function recursively
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // Select count from MHDR
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    
    for (i = 0; i < count; i++) {
        / / category_t
        category_t *cat = categorylist[i];
        // Get the corresponding class of this class
        Class cls = remapClass(cat->cls);
        // Continue if the class does not exist
        if(! cls)continue;  // category for ignored weak-linked class
        
        // Swift class extension and classification does not allow +load methods
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        
        // If the class is not implemented, implement the class, realizeClassWithoutSwift ends with append category data to the class.
        // If there is a +load function in the class, will the +load function in the class be overridden by the +load function in the class?
        // it will be overwritten 😂
        
        // Can be verified, prepare the parent and child class and a class of their own all implement +load function,
        // Add a [super load] call to the subclass's +load function,
        // The +load function of the parent class is executed
        The override process for load is exactly the same as any other custom function written in the class with the same name as this class.
        
        // The "overwrite" feature is not displayed when the system automatically calls the +load function.
        // add the load function directly to the global loadable_classes array.
        // Then add_class_to_loadable_list adds the class's +load function to the global loadable_classes array.
        // Class lookup +load is a list of lookup ro()->baseMethods() functions, which are appended to the RW.
        
        realizeClassWithoutSwift(cls, nil);
        // If not implemented, assert
        ASSERT(cls->ISA() - >isRealized());
        
        // add the +load function in cat to the global loadable_classes array
        add_category_to_loadable_list(cat); }}Copy the code

schedule_class_load

/* * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, And any categories in this image. * Schedule the +load function of the class in this image, and its UN -+load-ed parent in other mirrors, * and the +load function in any categories in this image. * /
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// Recursive scheduling +load functions for CLS and its UN -+load-ed other mirrors.
// cls must already be connected.
// CLS must already be linked.
static void schedule_class_load(Class cls)
{
    if(! cls)return;
    // If CLS is not implemented,
    // Normally all non-lazily loaded classes after _read_imags should already be implemented
    ASSERT(cls->isRealized());  // _read_images should realize

    // #define RW_LOADED (1<<23)
    // if CLS +load has already been called, return.
    // Determine the RW_LOADED bit of the class data()->flags. The load function is called only once for each class.
    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    // Schedule the parent recursively, ensuring that the parent's +load function executes first
    schedule_class_load(cls->superclass);

    // Put the CLS +load function into a global loadable_class array
    // Save the CLS and CLS +load functions
    add_class_to_loadable_list(cls);
    
    // Set CLS flag bit to +load already called
    cls->setInfo(RW_LOADED); 
}
Copy the code

add_class_to_loadable_list


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

// List of classes that need +load called (pending superclass +load)
// List of classes that need to be called with +load (superclass to be handled +load)
// This list always has superclasses first because of the way it is constructed
// The list always has a superclass because of the way it is constructed.

// Static global loadable_class array
static struct loadable_class *loadable_classes = nil;

// Record the used capacity of the loadable_classes array
static int loadable_classes_used = 0;

// Record the total capacity of the loadable_classes array
static int loadable_classes_allocated = 0;

/* * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load Method. * The CLS class has just established a connection. If the CLS implements a +load function, it schedules its +load function. * Puts the +load function in a global array of functions. * /
void add_class_to_loadable_list(Class cls)
{
    // A temporary variable that uses the +load function that receives CLS
    IMP method;
    / / lock
    loadMethodLock.assertLocked(a);// get the CLS +load function
    method = cls->getLoadMethod(a);if(! method)return;  // Don't bother if cls has no +load method
    
    // log
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    // If the global loadable_classes array is empty or full, expand it
    if (loadable_classes_used == loadable_classes_allocated) {
        // See that the initial length is 16, and then increase by 2 times each time
        // (16) (48 = 16 * 2 + 16) (112 = 48 * 2 + 16) (240 = 112 * 2 + 16)
        // Multiples of 16:1, 3, 7, 15, 31, 63...
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        
        // Just expand the capacity and do nothing about the order of the data
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    // Then place the class and the +load function in the array
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    
    / / from 1-6
    loadable_classes_used++;
}
Copy the code

getLoadMethod

/* * objc_class::getLoadMethod * fixme * Called only from add_class_to_loadable_list * * Locking: runtimeLock must be read-or write-locked by the caller. */
IMP 
objc_class::getLoadMethod(a)
{
    / / lock
    runtimeLock.assertLocked(a);// List of temporary variable error functions
    const method_list_t *mlist;

    ASSERT(isRealized());
    ASSERT(ISA() - >isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA() - >isMetaClass());
    
    Ro ()->baseMethods()
    // The +load function stored in it only comes from the +load function when the class is defined
    The +load function in the class is appended to the RW
    ISA()->data()->ro(), in its metaclass.
    
    mlist = ISA() - >data() - >ro() - >baseMethods(a);if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            if (0= =strcmp(name, "load")) {
                returnmeth.imp; }}}return nil;
}
Copy the code

Note that the list of ro()->baseMethods() functions holds only the +load function from the class definition. The +load function defined in the class definition is added to the RW.

add_category_to_loadable_list


struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};

// List of categories that need +load called (pending parent class +load)
// List of classes to be called with +load (parent class to be processed +load)

static struct loadable_category *loadable_categories = nil;

// Record the capacity used by the loadable_categories array
static int loadable_categories_used = 0;

// Record the total capacity of the loadable_categories array
static int loadable_categories_allocated = 0;

/* * add_category_to_loadable_list * Category cat's parent class exists and the category has been attached to its class. * Category CAT's subclass exists, and the Category data is already attached to it. * * Schedule this category for +load after its parent class * becomes connected and has its own +load method called. * The class's own +load function is scheduled only after its owning class establishes a connection and calls its own +load method. * * /
void add_category_to_loadable_list(Category cat)
{
    IMP method;
    
    / / lock
    loadMethodLock.assertLocked(a);// Find the +load function from cat classMethods
    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if(! method)return;

    // log
    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    Add_class_to_loadable_list add IMP of cat and load to loadable_categories array
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
Copy the code

call_load_methods

/* * call_load_methods * Call all pending classes and categories +load methods. * * Class +load methods are called superclass-first. * * Category +load methods are not called until after the parent class's +load The +load method in Category. * * This method must be re-entrant, because a +load could trigger more image mapping. Because +load may trigger more mirrors to be mapped, and then the mirrors have + loads to perform. In addition, the superclass-first ordering must be preserved In the face of re-entrant calls. In addition, the superclass-first ordering must be preserved In the face of re-entrant calls. Superclass priority is still retained. * * Therefore, only the OUTERMOST call of this function will do anything, * And that call will handle all loadable classes, even those generated while it was running. * Therefore, only OUTERMOST calls will perform any operations, This call will handle all loadable classes, even those generated at run time. * * The sequence below preserves +load ordering in the face of image loading during a +load, * and make sure that no +load method is forgotten because it was added during a +load call +load order, and make sure you haven't forgotten the +load method since it was added during the +load call. * * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 1. Repeat class +loads until there is no more * 2. Call category +loads ONCE. * 2. Load * 3. Run more +loads: * 3. Other cases of running the +load function: * (a) there are more classes to load, OR * (a) there are more classes to load, * (b) there are some potential category +loads that have still never been attached. * (b) Some potential category +loads that have not been attached. * Category +loads are only run once to ensure "parent class first" ordering, * even if a category +load triggers a new loadable class and a new loadable category attached to that class. * category The "+load" function calls a for loop, unlike the "class" function. Category (ONCE) ensures that the "class" load takes precedence over the "category" load. * Even if the class +load triggers a new loadable class, and a new loadable class is attached to that class, make sure that the class calls the +load function before the class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. */
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    / / lock
    loadMethodLock.assertLocked(a);// Re-entrant calls do nothing; the outermost call will finish the job.
    // Re-entrant is invalid and must wait for the outermost call to complete.
    if (loading) return;
    loading = YES;

    // Create an automatic release pool
    // push
    void *pool = objc_autoreleasePoolPush(a);do {
        // 1. Repeatedly call class +loads until there aren't any more
        // 1. Repeat class +loads until there is no more
        while (loadable_classes_used > 0) {
            call_class_loads(a); }// 2. Call category +loads ONCE
        // 2. Call the class +load function
        more_categories = call_category_loads(a);// 3. Run more +loads if there are classes OR more untried categories
        // 3. If there are classes or more uncalled categories, execute +load
    } while (loadable_classes_used > 0  ||  more_categories);
    
    // pop
    objc_autoreleasePoolPop(pool);

    loading = NO;
}
Copy the code

call_class_loads

/* * 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(). * This function is Called only by call_load_methods(). * /
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    // Detach the current loadable list.
    // Accept loadable_classes with a temporary variable
    struct loadable_class *classes = loadable_classes;
    
    // remove the occupation
    int used = loadable_classes_used;
    
    // set "raw data" to nil and set 0
    / / set to nil
    loadable_classes = nil;
    / / s
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    // Loop to the +load function
    
    for (i = 0; i < used; i++) {
        // Get the class
        Class cls = classes[i].cls;
        
        // get the function IMP and assign a pointer to it
        load_method_t load_method = (load_method_t)classes[i].method;
        
        if(! cls)continue;
        
        // log
        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        
        // Load is executed through the function pointer
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
Copy the code

call_category_loads

/* * call_category_loads * Call some pending category +load methods. * * The parent class of The +load-implementing categories has all of its categories attached, In case some are lazily waiting for +initalize. * * Don't call +load unless the parent class is connected. * * If new categories become loadable, +load is NOT called, and they are * added to the end of the loadable list, And we return TRUE. * If the new categories become loadable, instead of calling +load directly, they are added to the end of the loadable list and TRUE is returned. * Return FALSE if no new categories became loadable. * Return FALSE if there are no new categories to load. * * Called only by call_load_methods(). * only Called by call_load_methods(). * * /
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    // Use temporary variables to receive loadable_categories data and set the loadable_categories data to its initial state
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    
    // set to nil, set to 0
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    // Loop through the load function of cats
    for (i = 0; i < used; i++) {
        / / the cat
        Category cat = cats[i].cat;
        // Get the LOAD IMP and assign it to a function pointer
        load_method_t load_method = (load_method_t)cats[i].method;
        
        Class cls;
        if(! cat)continue;
        
        // Get the CLS to which the class belongs
        cls = _category_getClass(cat);
        
        // If CLS exists and isLoadable is true
        // isLoadable defaults to true
        // bool isLoadable() {
        // ASSERT(isRealized());
        // return true; // any class registered for +load is definitely loadable
        / /}
        
        if (cls  &&  cls->isLoadable()) {
            // log
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            // The load function executes
            (*load_method)(cls, @selector(load));
            
            // set cat to nilcats[i].cat = nil; }}// Compact detached list (order-preserving)
    // Assemble the cats element (cat is not nil) to the left of the array
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else{ shift++; }}// Shift records the number of cats array elements whose cat is nil
    // Used Indicates the current capacity occupied by CATS
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    // Copy all new +load candidates from loadable_categories to cats.
    // new_categories_added Records whether "+load" needs to be invoked
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        / / capacity
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        / / assignment
        / / usee since
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    // After copying the loadable_categories data into cats,
    / / release loadable_categories
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    // If cats currently has content, place the content of cats back to loadable_categories
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        // If not, release cats
        if (cats) free(cats);
        
        // set loadable_categories to nil and set 0
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    // log
    if (PrintLoading) {
        if(loadable_categories_used ! =0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); }}// new_categories_added = (loadable_categories_used > 0);
    return new_categories_added;
}
Copy the code

The whole idea is to collect the +load function from the class, the +load function from the class, and then execute it.

Refer to the link

Reference link :🔗

  • Analyze the Runtime in OC2.0 with the working principle of category
  • Understand Objective-C: categories in depth
  • IOS has a Category loading process and +load
  • IOS Runtime (17) : _dyLD_OBJC_notify_register
  • IOS Development Runtime (27): _read_images