Words Of Life

Load And Initialization Methods

  • 摘要:浅谈NSObject中的load方法与initialize方法


前言

  • 谈到NSObject根类,有朋友可能便会立即想到这两个方法,这两个方法究竟是什么?它们有什么区别,又有什么共同点呢?希望下文能够帮各位朋友重温load方法与initialize方法的要点。

+ (void)load;

  • 在运行时,涉及load的方法,大概调用过程如下图

    Load Photo
  • load方法在程序中的调用时间段:在App启动加载时调用

    1. 程序依赖的动态链接库加载进内存
    2. 加载可执行文件的所有符号、代码
    3. 运行时解析被编译过的符号代码,遍历所有类与分类,按继承关系依次调用类与分类的load方法
      • load方法一般在类/分类添加到运行时环境时调用,一般在application:willFinishLaunchingWithOptions:调用之前
  • Xcode - Targets - Build Phases - Complie Sources中的顺序为默认load方法的调用顺序,但并非最终的调用顺序,因为被依赖的类会优先调用load方法。比如子类B是父类A的子类,当把子类B放在Complie Sources的第一个文件时,编译运行后,会发现首先是父类的load方法先调用,然后再是子类的load方法调用,详细调用顺序请见下文

  • load方法中会自动加锁,保证线程安全

  • 调用顺序:依赖的类会先调用,即调用子类时,会先调用父类;分类最后调用,不会覆盖load方法

    • 简析:因为load方法在运行时并不是通过消息发送(objc_msgSend),而是通过函数指针来实现的。所以对于一个有继承关系的类,若子类中的load方法为空实现,则父类的load方法除因依赖问题被调用一次外,不会再次被调用。同理,因为不是走消息发送的原因,所以如果在分类中重写load方法,分类中的load方法并不会像其他方法一样覆盖原类的load方法,而是在原类load方法执行后执行
    • 例子A:假设分类B+C是子类B的分类,分类含有load方法。而子类B是父类A的子类,在AppDelegate.m中创建一个子类B对象,则load方法调用顺序如下:
      1. 调用父类A的load方法
      2. 调用子类B的load方法
      3. 调用分类B+C的load方法
  • 一般应用场景

+ (void)initialize;

  • 在运行时中,涉及initialize的方法,大概调用过程如下图

    Initialize Photo
  • initialize方法在程序中的调用时间段:第一次主动调用当前类时,即initialize方法永远是类收到的第一个消息。注意,上文提及的load方法因为是通过函数指针调用,并不是走消息发送的途径,所以尽管load方法在initialize方法调用前调用,但load方法依然不能称作第一个消息

  • initialize方法在线程安全的环境中执行,即只有执行initialize的那个线程可以操作类/类实例,其他线程都会被阻塞,等待initialize方法执行完

  • 调用顺序:依赖的类会先调用,即调用子类时,会先调用父类;当子类空实现时,会再次调用父类方法;分类会覆盖load方法

    • 简析:因为initialize方法在运行时是通过消息发送(objc_msgSend)来实现的。所以对于一个有继承关系的类,如果子类中的initialize方法为空实现,则父类的initialize方法会被自动调用(等价于我们系统帮我们默认在空实现中写了[super initialize])。同理,因为消息发送的原因,所以子类分类如果分类中重写initialize方法,分类中的initialize方法会像其他方法一样覆盖子类的initialize方法。

    • 例子B:假设子类B是父类A的子类,B中initialize方法为空实现,在AppDelegate.m中创建一个子类B对象,则initialize方法调用顺序如下:

      1. 调用父类A的initialize方法
      2. 再次调用父类A的initialize方法
      3. 调用子类B自身的initialize方法
    • 例子B补充:由于在父类A中,initialize方法会被调用两次,可在父类A使用以下代码,避免父类A的initialize方法多次执行初始化代码:

      codeA Photo
    • 例子C:假设分类B+C是子类B的分类,分类含有initialize方法,但该方法不含[super initialize]。而子类B是父类A的子类,子类B中initialize方法为非空实现,在AppDelegate.m中创建一个子类B对象,则initialize方法调用顺序如下:

      1. 调用父类A的initialize方法
      2. 调用分类B+C的initialize方法(注意,由于我们没写[super initialize],所以仅调用一次父类initialize方法,系统不会自动为我们增加该语句)
  • 一般应用场景

    • 用于初始化无法直接初始化的全局状态的static变量,如下,NSArray无法直接初始化,只能在initialize方法中对其进行初始化

      codeB Photo

对比表格

对比点 load方法 initialize方法
是否所有类都会调用
什么情况下会被调用 只要类所在的文件夹被引用 只要类的方法被调用
什么时候调用 被添加到运行时环境时 收到消息时/永远不调用
main方法前/后调用
调用时运行时环境 脆弱的运行时环境,自动释放池尚未建立 完整的运行时环境
调用本质 函数指针调用 消息发送(objc_msgSend)
系统是否最多调用一次 否,父类的可能被执行多次
空实现时,自动调用父类实现
父类、子类、分类的调用顺序 父类 -> 子类 -> 分类 父类 -> 分类/子类
是否线程安全

禁忌点

  • load方法
    • 不要在load方法中滥用其他类
      • 这是因为我们无法判断各个类的载入顺序,有可能会导致依赖环
    • 不要在load方法中滥用需要Autoreleasepool的方法,如[NSArray array]等方法
      • 这是因为此时运行时环境脆弱,Autoreleasepool尚未加载,如果确实要使用,需使用@Autoreleasepool代码块将其包围
    • 不要在load方法中包含复杂的代码,即使用时不能重度依赖该方法
      • 这是因为过于复杂的load方法会阻塞线程,导致程序无响应
    • 不要在load方法中包含等待锁/调用可能会加锁的方法
      • 这是因为我们无法判断各个类的载入顺序,有可能会导致死锁/饥饿
  • initialize方法
    • 绝不应该通过代码直接调用initialize方法
      • 这是因为我们应该保证initialize方法由系统调用,保证其仅调用一次
    • 不要在initialize方法中包含复杂的代码,即使用时不能重度依赖该方法
      • 这是因为其有可能阻塞UI线程
    • 不要在initialize方法中滥用其他类
      • 这是因为在本类还没有完全初始化的时候,却又被自身调用的类调用了,即有可能会导致依赖环

总结

  • load方法与initialize方法可以main方法为分界线,将调用顺序划分如下图,注意,其中initialize方法也有可能不调用

    Main Photo
  • load在运行时通过函数指针调用,initialize方法通过消息发送调用,这直接导致了他们之间的差异



  • Last Edited:2016.1.17
  • Author:@Seahub
  • Please contact me if you want to share this Article, 3Q~
五毛也是情, 一元也是爱