洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

      • OC基础之常见知识
      • OC基础之属性关键字
      • OC基础之底层系列
      • OC反射机制系列
      • 分类与扩展
      • load跟initialize
        • isa指针是什么
        • 引用计数管理
        • 字典系列
      • Runloop系列

      • Runtime系列

      • 内存管理系列

      • Block系列

      • 线程系列

      • KVC跟KVO系列以及通知中心

      • UI系列

      • 离屏渲染系列

      • 组件化系列跟架构

      • OC跟webview交互系列

      • 持久化系列

      • APP编译系列

      • APP性能优化系列

      • cocoapods系列

      • swift系列

      • Git系列

      • 网络相关

      • 三方库系列

      • 系统原理

      • 总结系列

      • 算法系列

      • 数据结构系列

    • 前端

    • 技术
    • iOS基础知识
    • iOS底层相关
    洋仔
    2024-09-05
    目录

    load跟initialize

    Objective-C 有两个神奇的方法:+load 和 +initialize,这两个方法在类被使用时会自动调用。但是两个方法的不同点会导致应用层面上性能的显著差异。

    小结:

    # 1、相同点

    1).load和initialize会被自动调用,不能手动调用它们。
    2).子类实现了load和initialize的话,会隐式调用父类的load和initialize方法。
    3).load和initialize方法内部使用了锁,因此它们是线程安全的。

    # 2、不同点

    1).调用顺序不同,

    1. 以main函数为分界,+load方法在main函数之前执行,
    2. +initialize在main函数之后执行。

    2).若自身未定义,是否沿用父类的方法:

    1. 子类中没有实现+load方法的话,不会调用父类的+load方法;(不会)
    2. 而子类如果没有实现+initialize方法的话,也会自动调用父类的+initialize方法。(会)

    3).调用时机:

    1. +load方法是在类被装载进来的时候就会调用,
    2. +initialize在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次,是懒加载模式,如果这个类一直没有使用,就不回调用到+initialize方法。

    4).调用方式:

    1. load是根据函数地址直接调用
    2. initialize是通过objc_msgSend调用
    方法 +(void)load +(void)initialize
    执行时机 在程序运行后立即执行 在类的方法第一次被调时执行
    若自身未定义,是否沿用父类的方法? 否(这是由于+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用。) 是
    分类中的定义 全都执行,但后于类中的方法 覆盖类中的方法, 只执行一个
    执行次数(非主动调用的情况下) 必然一次 0、1、多 次(调用者会不同)

    先看官方解释 (opens new window):

    1. 首先说一下 + initialize 方法:苹果官方对这个方法有这样的一段描述:这个方法会在 第一次初始化这个类之前 被调用,我们用它来初始化静态变量。

    2. load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,就会调用每个类的 + load 方法。

    3. 之后我们结合代码来探究一下 + initialize 与 + load 两个方法的调用时机,首先是 + load:

      #pragram ---main函数中的代码---
      #import <UIKit/UIKit.h>
      #import "AppDelegate.h"
      int main(int argc, char * argv[]) {
          NSLog(@"%s",__func__);
          @autoreleasepool {
              return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
          }
      }
      #pragram ---基于NSObject的Person类---
      #import "Person.h"
      @implementation Person
       + (void)load{
          NSLog(@"%s",__func__);
      }
       + (void)initialize{
          [super initialize];
          NSLog(@"%s %@",__func__,[self class]);
      }
       - (instancetype)init{
          if (self = [super init]) {
              NSLog(@"%s",__func__);
          }
          return self;
      }
      @end
      #pragram ---基于Person的Son类---
      #import "Girl.h"
      @implementation Girl
       + (void)load{
          NSLog(@"%s ",__func__);
      }
       + (void)initialize{
          [super initialize];
          NSLog(@"%s ",__func__);
      }
       - (instancetype)init{
          if (self = [super init]) {
              NSLog(@"%s",__func__);
          }
          return self;
      }
      @end
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43

    运行程序,我们看一下输出日志:

    2015-10-27 15:21:07.545 initialize[11637:334237] +[Person load]
    2015-10-27 15:21:07.546 initialize[11637:334237] +[Girl load] 
    2015-10-27 15:21:07.546 initialize[11637:334237] main
    
    1
    2
    3

    # 二、接下来我们来查看一下 + initialize 方法,先在 ViewController 中创建 Person 和 Girl 对象:

    #import "ViewController.h"
    #import "Person.h"
    #import "Son.h"
    #import "Girl.h"
    @interface ViewController ()
    @end
    @implementation ViewController
     - (void)viewDidLoad {
        [super viewDidLoad];
        Person * a = [Person new];
        Person * b = [Person new];
        Girl *c = [Girl new];
        Girl *d = [Girl new];
    }
    @end
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    下面我们来看一下输出日志:

    2015-10-27 15:33:56.195 initialize[11711:342410] +[Person load]
    2015-10-27 15:33:56.196 initialize[11711:342410] +[Girl load] 
    2015-10-27 15:33:56.197 initialize[11711:342410] main
    2015-10-27 15:33:56.259 initialize[11711:342410] +[Person initialize] Person
    2015-10-27 15:33:56.259 initialize[11711:342410] -[Person init]
    2015-10-27 15:33:56.259 initialize[11711:342410] -[Person init]
    2015-10-27 15:33:56.259 initialize[11711:342410] +[Girl initialize] 
    2015-10-27 15:33:56.260 initialize[11711:342410] -[Girl init]
    2015-10-27 15:33:56.260 initialize[11711:342410] -[Girl init]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    通过这个实验我们可以确定两点:
    - + initialize 方法类似一个懒加载,如果没有使用这个类,那么系统默认不会去调用这个方法,且默认只加载一次;
    - + initialize 的调用发生在 +init 方法之前。

    # 三、接下来再探究一下 + initialize 在父类与子类之间的关系,创建一个继承自 Person 类的 Son类:

    #pragram ---ViewController 中的代码---
    #import "ViewController.h"
    #import "Person.h"
    #import "Son.h"
    #import "Girl.h"
    @interface ViewController ()
    @end
    @implementation ViewController
     - (void)viewDidLoad {
        [super viewDidLoad];
        Person * a = [Person new];
        Person * b = [Person new];
        Son*z = [Son new];
    }
    @end
    
    
    2015-10-27 15:44:55.762 initialize[12024:351576] +[Person load]
    2015-10-27 15:44:55.764 initialize[12024:351576] +[Son load]
    2015-10-27 15:44:55.764 initialize[12024:351576] +[Girl load] 
    2015-10-27 15:44:55.764 initialize[12024:351576] main
    2015-10-27 15:44:55.825 initialize[12024:351576] +[Person initialize] Person
    2015-10-27 15:44:55.825 initialize[12024:351576] -[Person init]
    2015-10-27 15:44:55.825 initialize[12024:351576] -[Person init]
    2015-10-27 15:44:55.826 initialize[12024:351576] +[Person initialize] Son
    2015-10-27 15:44:55.826 initialize[12024:351576] -[Person init]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    我们会发现 Person 类的 + initialize 方法又被调用了,但是查看一下是子类 Son 调用的,也就是创建子类的时候,子类会去调用父类的 + initialize 方法。

    # 四、总结

    • 如果你实现了 + load 方法,那么当类被加载时它会自动被调用。这个调用非常早。
      如果你实现了一个应用或框架的 + load,并且你的应用链接到这个框架上了,那么 + load 会在 main() 函数之前被调用。
      如果你在一个可加载的 bundle 中实现了 + load,那么它会在 bundle 加载的过程中被调用。比如方法交换等
    • + initialize 方法的调用看起来会更合理,通常在它里面写代码比在 + load 里写更好。
      + initialize 很有趣,因为它是懒调用的,也有可能完全不被调用。类第一次被加载时,

    # 一、+load 调用时机和顺序原理解析

    load 方法在什么时候调用?
    官方解释是:运行时,添加类或者分类的时候调用。实现此方法以在加载时执行特定于类的行为。

    +load方法是一定会在runtime中被调用的,只要类被添加到runtime中了,就会调用+load方法,即只要是在Compile Sources中出现的文件总是会被装载,与这个类是否被用到无关,因此+load方法总是在main函数之前调用。所以我们可以自己实现+laod方法来在这个时候执行一些行为。

    换句话说,load是在app启动的时候加载并在main函数之前调用****,从源码中找

    _objc_init —>
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    1
    2

    这里面的2个方法 map_images 和 load_images, map_images的作用就是加载所有的类/协议/分类,加载完成之后,就开始调用load_images,在这个方法里面看:

    load_images(const char *path __unused, const struct mach_header *mh)
    {
        ......
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);    // 把所有需要load的类 加载一个list里面
        }
        call_load_methods();    // 调用load方法
    }
    
    
    void call_load_methods(void)
    {
       ......
        do {
            while (loadable_classes_used > 0) {
                call_class_loads();                     // 先加载类的load 
            }
            more_categories = call_category_loads();    // 在加载category的load
    
        } while (loadable_classes_used > 0  ||  more_categories);   
       ......
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    另外有意思的一点是:

    +load方法不会覆盖(因为**由于+load方法是根据方法地址直接调用,**并不是经过objc_msgSend函数调用)。
    也就是说,如果子类实现了+load方法,那么会先调用父类的+load方法,然后又去执行子类的+load方法。
    但要注意的时+load方法只会调用一次。而且执行顺序是:类 ->父类-> 子类 ->分类。而不同类之间的顺序不一定。

    但是这里依然有一个疑问,官方解释没有说清楚,1、分类的加载顺序是怎样的?
    其实在源码中有可以看到:

    void prepare_load_methods(const headerType *mhdr)
    {
    ......
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);    // 1、按照编译顺序加载所有的类(不包括分类)
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));  // 2、在这里  按照先父类 在子类的方式加入列表
        }
    
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);  // 分类也是类似的方式
        for (i = 0; i < count; i++) {
           ......
            add_category_to_loadable_list(cat);
        }
    }
    
    
    static void schedule_class_load(Class cls)
    {
      .......
        schedule_class_load(cls->superclass);    //递归加载,先加载父类
    
        add_class_to_loadable_list(cls);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    由以上代码中 1、2备注,得出 :

    1、先加载类的load:类的加载是按照编译顺序,同时遵循先父类再子类的方式
    2、再加载分类的load:分类直接按照编译顺序,和其绑定类的继承没有关系

    # 总结

    1、先加载类的load
    2、再加载分类的load
    3、不同的类之间加载load顺序为:有继承关系的,先加载父类load、再加载子类的load,无继承关系的,按照编译顺序
    ----比如顺序二 Student、OtherClass、Person,先加载Student的load,由于Person是Student的父类,所以Person的顺序比OtherClass早
    4、分类的加载顺序是完全按照编译顺序,也就是谁在前面,谁先加载。和其绑定类的继承关系无关
    ----比如顺序二中,Student继承Person,但是其分类的顺序是 Student+JE2、Student+JE1、Person+JE,顺序是什么样,加载load就是什么样。
    5、即使有类的源文件,但是编译列表中没有,那么这个类就不会被编译,也就不会执行其load方法

    # 2、+initialize

    initialize方法在什么时候调用?
    官方解释是:在类收到第一条消息之前初始化它。
    换句话说,就是第一次用到它之前调用,比如初始化一个对象(其父类也会调用initialize)、调用类方法等。 从源码中找:

    • 说明:没有发送消息的类不会调用initialize
    • 如果主类有相应的分类(或多个分类),会调用分类中的initialize方法,具体调用的是哪个分类的方法,由编译顺序决定。
    • 当子类没有重写initialize方法,这个时候回去执行父类的initialize方法
    class_initialize -> initializeNonMetaClass()
    void initializeNonMetaClass(Class cls)
    {
        .......
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            initializeNonMetaClass(supercls);        // 递归加载父类的initialize
        }
        ........
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    +initialize方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用,并且只会调用一次。
    +initialize方法实际上是一种惰性调用,也就是说如果一个类一直没被用到,那它的+initialize方法也不会被调用,这一点有利于节约资源。

    与+load方法不同,却更符合我们预期的就是,+initialize方法会覆盖是因为调用的objc_msgSend。
    也就是说如果子类实现了+initialize方法,就不会执行父类的了,直接执行子类本身的。
    如果分类实现了+initialize方法,也不会再执行主类的。
    所以+initialize方法的执行覆盖顺序是:分类 -> 子类 ->类。且只会有一个+initialize方法被执行。

    # 总结

    1、initialize的执行顺序为:分类 -> 子类 ->类 2、分类的initialize方法会覆盖主类的方法(假覆盖,方法都在,只是没有执行)
    3、只有在这个类有发送消息的时候才会执行initialize,比如初始化对象、调用类方法等。
    4、多个分类的情况,只执行一次,具体执行哪个分类的initialize,有编译顺序决定(Build Phases -> Compile Sources 中的顺序)
    5、如果子类没有重写initialize,那么会调用其父类的initialize方法

    编辑 (opens new window)
    上次更新: 2024/10/23, 23:26:17
    分类与扩展
    isa指针是什么

    ← 分类与扩展 isa指针是什么→

    最近更新
    01
    数组
    10-25
    02
    数组双指针系列之对撞指针
    10-25
    03
    数组双指针系列之快慢指针
    10-25
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式