+ the load () method

The +load() method is called when the class/class is first loaded, and the +load() method is called for each class/class

  • Test 1, parent class first, child class second

  • Test 2, parent class after, child class first

Based on the first two tests, we can see that the +load() method of the parent class is called before that of the subclass, regardless of the compile order, while the +load() method of the class is called in the compile order

The source code parsing

Let’s look at the source code

  1. Load_images () function

The load_images() function preloads and calls the load() method.

void load_images(const char *path __unused, const struct mach_header *mh)
{
    if(! didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories =true;
        loadAllCategories(); // Load the classification
    }

    // If there is no load() method, return directly
    if(! hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // Preload () method
        prepare_load_methods((const headerType *)mh);
    }

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

Copy the code
  1. Prepare_load_methods () function

    • preload
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            // Load the class's load() method
            schedule_class_load(remapClass(classlist[i]));
        }
    
        category_t * const *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");
            }
            realizeClassWithoutSwift(cls, nil);
            ASSERT(cls->ISA()->isRealized());
            
            // Load () to load the class
            add_category_to_loadable_list(cat);
            /** get load() method = _category_getLoadMethod(cat); "// Assign the elements of the list to loadable_categories[loadable_categories_used]. Cat = cat; loadable_categories[loadable_categories_used].method = method; // Count +1 loadable_categories_used++; * /}}Copy the code
    • Schedule_class_load () function
    static void schedule_class_load(Class cls)
    {
        if(! cls)return;
        ASSERT(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
        // Add the load method to the list, so that's why the parent class is called first
        schedule_class_load(cls->getSuperclass());
        
        // Add to list
        add_class_to_loadable_list(cls);
        
        // load() method = CLS ->getLoadMethod(); Loadable_classes [loadable_classes_used]. CLS = CLS; loadable_classes[loadable_classes_used].method = method; // count +1 loadable_classes_used++; * /
        cls->setInfo(RW_LOADED); 
    }
    Copy the code

    The schedule_class_load() function is called recursively to the parent class, so the parent’s +load() method is executed first

  2. Call the +load() method

    • The loop calls the +load() method
    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;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads 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);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    Copy the code
    • call_class_loads()
    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;
            load_method_t load_method = (load_method_t)classes[i].method;
            if(! cls)continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            // Execute the load() function
            (*load_method)(cls, @selector(load));
        }
    
        // Destroy the detached list.
        if (classes) free(classes);
    }
    Copy the code
    • call_category_loads()
    static bool call_category_loads(void)
    {
        int i, shift;
        bool new_categories_added = NO;
    
        // Detach current loadable list.
        struct loadable_category *cats = loadable_categories;
        int used = loadable_categories_used;
        int allocated = loadable_categories_allocated;
        loadable_categories = nil;
        loadable_categories_allocated = 0;
        loadable_categories_used = 0;
    
        // 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 () is called directly after the load() function, same as call_class_loads(*load_method)(cls, @selector(load)); cats[i].cat = nil; }}/ /... Other code
        return new_categories_added;
    }
    Copy the code

    Load () is called directly from the C function. So, all load() methods of a class/class are executed once

+ the initialize () method

The +initialize() method is called the first time the class receives a message, meaning it will never be called unless the class is used. We also do two tests

  • Test 1, parent first

  • Test 2, the parent class comes after

The +initialize() method of the parent class is called regardless of whether the parent class is first or last. However, the order in which the +initialize() method is called depends on the order in which the parent class is compiled. This is similar to the results in the previous article, when we explored method calls in classifications. Below we look at the reason through the source code

The source code parsing

  1. lookUpImpOrForward()
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    // if it is initialized, mark it once
    if(slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimeLock.lock(); checkIsKnownClass(cls);// Load the CLS
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    / /... Perform other operations
    return imp;
}
Copy the code
  1. realizeAndInitializeIfNeeded_locked()
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    if(slowpath(! cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);// runtimeLock may have been dropped but is now locked again
    }

    // If CLS has not already been initialized, initialize it
    if(slowpath(initialize && ! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); }return cls;
}
Copy the code
  1. initializeAndLeaveLocked()
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
     // a call was made
     return initializeAndMaybeRelock(cls, obj, lock, true);
}
Copy the code
  1. initializeAndMaybeRelock()
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {
        if(! leaveLocked) lock.unlock();return cls;
    }
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    if (nonmeta->isRealized()) {
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        cls = object_getClass(nonmeta);
    }

    ASSERT(nonmeta->isRealized());
    // Initialize by constructing nonmetaclass
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}
Copy the code
  1. InitializeNonMetaClass () core code
void initializeNonMetaClass(Class cls)
{ ASSERT(! cls->isMetaClass()); Class supercls;bool reallyInitialize = NO;

    // Recursively calls the initialization method of the parent class
    supercls = cls->getSuperclass();
    if(supercls && ! supercls->isInitialized()) { initializeNonMetaClass(supercls); }/ /... Other code
    
    if (reallyInitialize) {
        / /... Other code
#if __OBJC2__
        @try
#endif
        {   // Initialize the class
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", objc_thread_self(), cls->nameForLogging()); }}#if __OBJC2__
        @catch(...). {if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }else {
            / /... Other code}}Copy the code
  1. callInitialize()
void callInitialize(Class cls)
{((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
Copy the code
  • As you can see from Step 5, the parent class is initialized recursively before initialization, so the +initialize() method of the parent class is called before the subclass
  • As you can see from Step 6, the last call is objc_msgSend(), which means that the final process goes to the Message sending logic of the Runtime. In the last article on categories, we explained how categories work"Cover"That’s why calling +initialize() on the parent class actually calls the classification method

+ the load (), plus the initialize ()

  1. Call the number
    • Will only be called once
  2. Call time
    • + Load (): called when the Runtime loads a class/class
    • +initialize(): called when the class receives a message for the first time (class initialization)
  3. Call the principle
    • +load(): calls directly using C functions
    • +initialize(): this is called by the Runtime objc_msgSend()
  4. Call to order
    • +load(): All classes/classes are called, the parent class first, and the compiled class first
    • +initialize(): Only one method of the parent class is called(A parent or a classification of a parent), the parent class is called first, and the methods of the parent class are called once for each class initialization(If the subclass is not implemented)