2017.08.08

This problem is a bit rehash, wrote the demo to more in-depth understanding of this problem, as a simple note

What is the experience of Load and Initialize?
  1. Which is loaded first, Load or Initialize?
  2. What’s the relationship between parent and subclass and Category?
  3. What about multiple categories?
Load

Direct interpretation of development documentation

  • The +load methods for all classes and classes are called when they are added to the Runtime
  • The parent class takes precedence over the subclass loading (internally, recursively)
  • Load each category after all the classes have been loaded
  • For the loading order of multiple classes for the same class, see Target -> Build Phases -> Compile Sources

For example, the existing Student inherits from Person, Student has multiple categories, and prints in the + load method of each class, resulting in the following result

2017- 0626 - 18:30:59.857400+0800 load[70593:5621604] Person ==> Load
2017- 0626 - 18:30:59.857596+0800 load[70593:5621604] Student ==> Load
2017- 0626 - 18:30:59.857656+0800 load[70593:5621604] Test2 ==> Load
2017- 0626 - 18:30:59.857707+0800 load[70593:5621604] Test1 ==> Load
2017- 0626 - 18:30:59.857724+0800 load[70593:5621604] Student + load2 ==> Load
2017- 0626 - 18:30:59.857730+0800 load[70593:5621604] Student + load3 ==> Load
2017- 0626 - 18:30:59.857736+0800 load[70593:5621604] Student + load1 ==> Load
Copy the code

Look at Compile Sources and observe the loading order of the Student classes as in the order of the load calls

Runtime runtime

First take a look at the void prepare_load_methods(header_info *hi) function in objC-Runtime-new.mm, where the necessary conditions for classes and classes to perform the +load method are prepared for subsequent calls

void prepare_load_methods(header_info *hi)
{
    size_t count, i;

    rwlock_assert_writing(&runtimeLock);

    classref_t *classlist =
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &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 classrealizeClass(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}Copy the code

Static void schedule_class_load(Class CLS)

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

    if (cls->data()->flags & RW_LOADED) return;
# warning The +load method that calls the parent class first and then the child class is implemented using recursion
    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
}
Copy the code

All classes and categories that currently meet the requirements for the +load method call are stored in the global variables loadable_classes (load_images method call) and loadable_categories, respectively

With your classes and classes ready, it’s time to call their +load method. Open objc-loadMethod. m and find void call_load_methods(void).

void call_load_methods(void)
{
    static BOOL loading = NO;
    BOOL more_categories;

    recursive_mutex_assert_locked(&loadMethodLock);

    // 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

Again, this function calls the +load method on the classes and classes prepared in the previous step and ensures that the class takes precedence over the class order. Let’s move on to the other two key functions called in this function: static void call_class_loads(void) and static BOOL call_category_loads(void). Since the two functions are very similar, the smaller static void call_class_loads(void) function is used as an example

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());
        }
        (*load_method)(cls, SEL_load);
    }

    // Destroy the detached list.
    if (classes) _free_internal(classes);
}
Copy the code

This function is actually responsible for calling the class’s +load method. It takes all callable classes from the global variable loadable_classes and zeros them out.

// Point to the first address of the memory used to hold the class information
loadable_classes = nil;
// Identifies the size of the allocated memory space
loadable_classes_allocated = 0;
// Identifies the amount of memory space used
loadable_classes_used = 0;
Copy the code

load_images -> load_images_nolock -> prepare_load_methods -> schedule_class_load -> add_class_to_loadable_list Unloaded classes are added to loadable_classes

The +load method for calling classes and classes directly uses the memory address of the function (*load_method)(CLS, SEL_load); Instead of sending the objc_msgSend message. Before the load method is called, all the framework is already loaded into the runtime, so it is safe to call methods in the framework

Initialize

First, take a look at the apple documentation for the definition of Initialize

Open objc-Runtime-new.mm and find the following functions

// When we send a message to a class, the Runtime calls this function to find the implementation or forward of the corresponding method in the class
# Warning Considering the above, the +initialize method is not called until the class receives the first message
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    ...
        rwlock_unlock_write(&runtimeLock);
    }
    # Warning: When a Class is not initialized, the Runtime calls void class_initialize(Class CLS)
    if(initialize && ! cls->isInitialized()) { _class_initialize (_class_getNonMetaClass(cls, inst));// 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
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could . }Copy the code

Let’s look at the actual initialization code

void _class_initialize(Class cls)
{
    ...
    Class supercls;
    BOOL reallyInitialize = NO;
# Warning also uses recursion to implement the order of calling the parent class first and then the child class
    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if(supercls && ! supercls->isInitialized()) { _class_initialize(supercls); }// Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if(! cls->isInitialized() && ! cls->isInitializing()) { cls->setInitializing(); reallyInitialize =YES;
    }
    monitor_exit(&classInitLock);

    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.

        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         cls->nameForLogging());
        }
#warning Notice that objc_msgSend is used here, which means that this method is just like any other normal method. Subclasses use the methods of their parent class, and the classes overwrite the methods in this class.
#warning Also, if the subclass does not implement the method but the parent class implements the method, the parent class's method must be implemented multiple times
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]". }Copy the code
  • For categories, the Initialize method overrides the methods in the Category (just like any other normal method).
  • Inside the Initialize method you can assign static variables that are not easy to Initialize at compile time
#warning Person.m

// Basic types such as int can be assigned at compile time
static int numCount = 0; 
// The object cannot be assigned in the compiler
static NSMutableArray *dataSource;

+ (void)initialize {
    if (self == [Person class]) {
        // Objects that cannot be assigned at compile time are assigned here
        dataSource = [[NSMutableArrayalloc] init]; }}Copy the code
Exception: A class method of B is called in A load method of class A
@implementation Father
+ (void)load {
    NSLog(@"father==> load===%@", [Dog class]); } + (void)initialize {
    NSLog(@"Father===>initialize");
}
@end

#warning The following output is displayed
2017- 08- 09 11:19:09.838 tests[34274:8415363] Dog===>initialize
2017- 08- 09 11:19:09.839 tests[34274:8415363] father==> load===Dog
2017- 08- 09 11:19:09.839 tests[34274:8415363] Dog==> load
2017- 08- 09 11:19:09.840 tests[34274:8415363] child==> load
2017- 08- 09 11:19:09.840 tests[34274:8415363] child + hahha==> load
2017- 08- 09 11:19:09.840 tests[34274:8415363] main
Copy the code

Here is a screenshot of Compile Source

conclusion
  • Normally, the load and Initialize methods are called before the object is instantiated. Load is the equivalent of a load method and is called before main(), while Initialize is called after main().
  • The initialize method of class B will be called before the load method of class A is called, but the load methods of class B will be loaded in the same order as the Compile Source
  • The load method of all classes is called, first the parent class, then the child class, and the classes are loaded in the order Compile Sources. However, the Initialize method is overridden and only one of the subclasses’ parent classes is executed
  • The load Method is used internally to implement Method Swizzle and the Initialize Method is used to Initialize global or static variables
  • Neither method can be called actively, and there is no need to inherit the parent method via super, but the Initialize method will call the parent method if the subclass does not implement it, whereas the load method will not