OC load 和 initialize 方法

OC中有两个特殊的类方法,分别是loadinitialize

先来看看NSObject Class Reference里对这两个方法说明:

+(void)load

The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.// 只要类或类别实现了load方法, 不论这个类或类别是动态加载还是静态链接, 类/类别都会受到load消息。


The order of initialization is as follows:

  1. All initializers in any framework you link to. // 调用所有的Framework中的初始化方法
  2. All +load methods in your image. // 调用所有的+load方法
  3. All C++ static initializers and C/C++ __attribute__(constructor) functions in your image. // 调用C++的静态初始化方及C/C++中的attribute(constructor)函数
  4. All initializers in frameworks that link to you. // 调用所有链接到目标文件的framework中的初始化方法

In addition:

  • A class’s +load method is called after all of its superclasses’ +load methods.
  • A category +load method is called after the class’s own +load method.
  • 先父类load-> 在子类load->最后category中load(父类的category优先级也低于子类)

In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet. // 在自定义的实现+load方法中,可以安全地向同一二进制包中的其它无关的类发送消息,但接收消息的类中的+load方法可能尚未被调用。

 

+(void)initialize

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses. // 在这个类接受第一个消息之前, runtime人会给每一个类发送initialize消息, 先父类在子类, initialize会保证线程安全

Apple的文档很清楚地说明了initialize和load的区别在于:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。

load

顾名思义,load方法在这个文件被程序装载时调用。只要是在Compile Sources中出现的文件总是会被装载,这与这个类是否被用到无关,load方法总是在main函数之前调用。

调用规则

如果一个类实现了load方法,在调用这个方法前会首先调用父类的load方法。而且这个过程是自动完成的,并不需要我们手动实现,由于load函数是系统自动加载的,因此不需要再调用[super load],否则父类的load函数会多次执行。:

// In Parent.m
+ (void)load {
    NSLog(@"Load Class Parent");
}

// In Parent+load.m
+ (void)load {
    NSLog(@"Load Class Parent+load");
}

// In Child.m,继承自Parent
+ (void)load {
    NSLog(@"Load Class Child");
}

// In Child+load.m,Child类的分类
+ (void)load {
    NSLog(@"Load Class Child+load");
}

// 运行结果: Parent+load和Child+load的顺序和文件编译顺序有关
/*
    2016-02-01 21:28:14.379 load[11789:1435378] Load Class Parent
    2016-02-01 21:28:14.380 load[11789:1435378] Load Class Child
    2016-02-01 21:28:14.380 load[11789:1435378] Load Class Parent+load
    2016-02-01 22:28:14.381 load[11789:1435378] Load Class Child+load
*/

执行顺序

load方法调用时,系统处于脆弱状态,如果调用别的类的方法,且该方法依赖于那个类的load方法进行初始化设置,那么必须确保那个类的load方法已经调用了,比如demo中的这段代码,打印出的字符串就为null

// In Child.m
+ (void)load {
    NSLog(@"Load Class Child");

    Other *other = [Other new];
    [other originalFunc];

    // 如果不先调用other的load,下面这行代码就无效,打印出null
    [Other printName];
}

load方法的调用顺序其实有迹可循,在Compile Sources中,文件的排放顺序就是其装载顺序,自然也就是load方法调用的顺序。这一点也证明了load方法中会自动调用父类的方法,因为在demo的输出结果中,Parentload方法先于Child调用,而它的装载顺序其实在Child之后。

虽然在这种简单情况下我们可以辨别出各个类的load方法调用的顺序,但永远不要依赖这个顺序完成你的代码逻辑。一方面,这在后期的开发中极容易导致错误,另一方面,你实际上并不需要这么做。

使用场景

由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load方法中。

一个常见的使用场景是在load方法中实现Method Swizzle:

// In Other.m
+ (void)load {
    Method originalFunc = class_getInstanceMethod([self class], @selector(originalFunc));
    Method swizzledFunc = class_getInstanceMethod([self class], @selector(swizzledFunc));

    method_exchangeImplementations(originalFunc, swizzledFunc);
}

Child类的load方法中,由于还没调用Otherload方法,所以输出结果是"Original Output",而在main函数中,输出结果自然就变成了"Swizzled Output"。

一般来说,除了Method Swizzle,别的逻辑都不应该放在load方法中实现。

runtime中的源码 

load调用方式

+load是通过函数指针指向函数,拿到函数地址,分开来直接调用的,直接通过内存地址查找调用的。

initialize

这个方法在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次。initialize方法实际上是一种惰性调用,也就是说如果一个类一直没被用到,那它的initialize方法也不会被调用,这一点有利于节约资源。

调用规则

load方法类似的是,在initialize方法内部也会调用父类的方法,而且不需要我们显示的写出来。与load方法不同之处在于,即使子类没有实现initialize方法,也会调用父类的方法,这会导致一个很严重的问题:

// In Parent.m
+ (void)initialize {
    NSLog(@"Initialize Parent, caller Class %@", [self class]);
}

// In Child.m
// 注释掉initialize方法

// In main.m
Child *child = [Child new];

运行后发现父类的initialize方法竟然调用了两次:

2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Parent
2016-02-01 22:57:02.985 load[12772:1509345] Initialize Parent, caller Class Child

这是因为在创建子类对象时,首先会调用一次父类的initialize方法,然后创建子类时,尽管child自己没有实现initialize方法,但是通过obj_msgsend调用,会找到父类的方法,然后又调用了一次。

虽然initialize方法对一个类而言只会调用一次,但这里由于出现了两个类,所以调用两次符合规则,但不符合我们的需求。正确使用initialize方法的姿势如下(官方文档上这么说的):

// In Parent.m
+ (void)initialize {
    if (self == [Parent class]) {
        NSLog(@"Initialize Parent, caller Class %@", [self class]);
    }
}

加上判断后,就不会因为子类而调用到自己的initialize方法了。

最后结论(参考链接 https://www.jianshu.com/p/c52d0b6ee5e9) : 

  • 1.父类的initialize方法会比子类先执行
  • 2.当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
  • 3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

 

使用场景

initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译器初始化:

// In Parent.m
static int someNumber = 0;     // int类型可以在编译期赋值
static NSMutableArray *someObjects;

+ (void)initialize {
    if (self == [Parent class]) {
        // 不方便编译期复制的对象在这里赋值
        someObjects = [[NSMutableArray alloc] init];
    }
}

initialize调用方式

在类第一次接收到消息时调用,消息转发机制调用的

void _class_initialize(Class cls)
{

    ... ...

    // 递归调用父类的_class_initialize方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
   
    
      // 将要调用SEL_initialize
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         cls->nameForLogging());
        }
      // 通过消息机制调用SEL_initialize
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
     // 调用完成
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         cls->nameForLogging());
        }        
        
       

总结

  1. loadinitialize方法都会在实例化对象之前调用,在runtime初始化完成后调用,后者在收到第一条消息前调用。这两个方法会被自动调用,不能手动调用它们 , 不要写 [super load] / [super initialize]。
  2. loadinitialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
  3. load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
  4. loadinitialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。
  5. +load调用方式:+load是通过函数指针指向函数,拿到函数地址,分开来直接调用的,直接通过内存地址查找调用的。
    +initialize:在类第一次接收到消息时调用,消息转发机制调用的(objc_msgSend). 
                    +initialize方法调用顺序, 所以类别中的initialize会覆盖主类的initialize.  
                    如果子类没有实现+initialize方法,会调用父类的+initialize(所以父类的+initialize方法可能会被调用多次)

 

原文链接:http://www.jianshu.com/p/d25f691f0b07

 +(void)load+(void)initialize
执行时机在程序运行后立即执行,runtime初始化中,main函数前在类的方法第一次被调时执行
若自身未定义,是否沿用父类的方法?
类别中的定义全都执行,但后于类中的方法覆盖类中的方法,只执行一个

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值