OC原理-load和initialize

172 阅读4分钟

一、+load方法

@implementation Person
+(void)load{
    NSLog(@"%s",__func__);
}
+(void)test{
    NSLog(@"%s",__func__);
}
@end
@implementation Person (test1)
+(void)load{
    NSLog(@"%s",__func__);
}
+(void)test{
    NSLog(@"%s",__func__);
}
@end
@implementation Person (test2)
+(void)load{
    NSLog(@"%s",__func__);
}
+(void)test{
    NSLog(@"%s",__func__);
}
@end

执行上面代码我们+test符合我们上面提及的方法覆盖,但类和分类的+load方法都执行了,即分类中的+load没有覆盖原类中的+load方法。

void printSelectorNameOfClass(Class cls){
    unsigned int count;
    Method *methodList =  class_copyMethodList(cls, &count);
    
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        NSString * methodName =  NSStringFromSelector(method_getName(method));
        IMP imp =  method_getImplementation(method);
        NSLog(@"%@",methodName);
//        NSLog(@"%p", imp);
    }
    NSLog(@"done");
    free(methodList);
}

printSelectorNameOfClass(object_getClass([Person class]));

通过上面的代码打印出Person原类对象中的方法列表,发现runtime也把分类中的+load方法合并到了原类对象中。那为何+load方法执行了多次呢???

void call_load_methods(void)
{
    do {
        // 1. 调用类的+load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2. Call category +loads ONCE  调用类的分类的+load方法
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
}
//调用所有类的+load方法
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes; //它生成规则决定了原类+load方法的执行顺序。
    int used = loadable_classes_used; 
    // 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方法
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}


static bool call_category_loads(void)
{
    
    struct loadable_category *cats = loadable_categories;//它生成规则决定了分类+load方法的执行顺序。
    int used = loadable_categories_used;
    // 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方法
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }
 }
 
 loadable_classes和loadable_categories生成规则在方法prepare_load_methods中,可以看到loadable_classes生成过程即考虑编译顺序,又考虑继承关系,loadable_categories只跟分类的编译顺序相关。

注意:通过上面的源码我们发现系统调用+load不是通过消息发送机制,而且先执行所有类的+load方法,再去执行所有分类的+load方法。

结论:1.+load方法会在runtime加载类、分类时调用。2.每个类、分类的+load方法,在程序运行过程中就调用一次。3.先执行所有原类的+load方法,再去执行所有分类的+load方法。4.原类的+load方法按照编译顺序执行(先编译,先调用),也遵循调用原类的+load方法,先调用其父类的+load方法。5.分类的+load执行顺序按照编译顺序执行(先编译,先调用)。

二、+initialize方法

@implementation Person
+(void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Person (test1)
+(void)initialize{
    NSLog(@"%s",__func__);
}
@end
@implementation Student
//+(void)initialize{
//    NSLog(@"%s",__func__);
//}
@end
@implementation Teacher
//+(void)initialize{
//    NSLog(@"%s",__func__);
//}
@end
其中Teacher、Student类继承于Person类
[Student alloc];
[Teacher alloc];

发现Person的分类中的+initialize方法执行了3次!下面我们来探究下+initialize方法的调用规则。

消息发送的方法查找过程中调用了+initialize方法。

//查找类方法,
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    //把元类对象传进去 
    return class_getInstanceMethod(cls->getMeta(), sel);
}
//查找实例方法
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
    return _class_getMethod(cls, sel);
}
lookUpImpOrForward--》initializeAndLeaveLocked--》initializeAndMaybeRelock--》initializeNonMetaClass
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;
    // 调用本类的initialize前递归调用其父类的initialize
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    // Try to atomically set CLS_INITIALIZING.
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);
        //本类被初始化并且没有正在初始化才能调用initialize  
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            //标示可以调用initialize
            reallyInitialize = YES;
            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    if (reallyInitialize) {
        {	
        	//调用initialize方法
            callInitialize(cls);
        }
        return;
    }

    else if (cls->isInitialized()) {
        //本类已经被初始化了,不在调用initialize
        return;
    } 
}
//这里才真正调用initialize方法
void callInitialize(Class cls)
{
	//通过objc_msgSend调用initialize方法
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
}

结论:1.initialize方法是通过消息发送调用的。2.+initialize方法会在类第一次接收到消息时调用。3.先调用父类+initialize方法,再调用子类的+initialize方法。4.当子类没有实现+initialize方法,会去调用父类的+initialize方法(父类的initialize方法可能会调用多次)。5.如果分类实现了+initialize,会覆盖原有类的+initialize方法。1,2,3通过源码可以看出来,4,5是通过消息发送机制决定的。

三、两者比较

load、initialize方法的区别
1.调用方式
1load是根据函数地址直接调用
2》initialize是通过objc_msgSend调用
2.调用时刻
1load是runtime加载类、分类的时候调用(只会调用一次)
2》initialize是类第一次接受到消息进行初始化时调用。
3.调用顺序
1load
1)先调用所有类的load; 排序规则:A先编译的类,优先调用load;B调用子类的load前,回先调用父类的load
2)在调用所有分类的load; 排序规则:先编译的分类,优先调用load
2》initialize :先调用父类的initialize,在调用子类的initialize。由于每个类只会初始化一次,原则上每个类的initialize都只会调用一次,但当子类没有实现initialize方法,子类初始化时要调用initialize方法就会进行方法查找,进而去调用父类的initialize方法,所以父类的initialize可能会调用多次。