+load 方法
1. 如果一个类实现了load 方法,那么在类被加载到内存的时候就会调用,与这个类是否被用到无关。执行在main函数之前,此时运行环境不安全,不能在这份方法里做过多的操作
当 class 或者 category 添加到 runtime 时调用。即 load 是在这个文件被程序加载时调用,注意:在 load方法中向其它类发送消息,接收消息的类中的+load方法可能尚未被调用。此时不能保证所有的类被加载完成。
2. 程序中所有的类的 load方法都会被系统自动调用,包括各种分类,每个类都只调用一次,并会隐式调用父类的load 方法。
注意:分类和类 load 方法都会调用,他们分别存储在两个全局表中。是分类 load 不覆盖原来类的 load 方法的本质;
两个 while 循环,先通过 call_class_loads 执行所有类的 load 方法,再通过 call_category_loads 执行分类的 load 方法。
3. 调用类的 load 方法前,先调用父类的 load方法(递归),父类方法优先于子类调用,分类load 方法 最后调用。
注⚠️: load 直接用函数地址调用,不是objc_msgSend 发送消息,不存在方法查找和消息传递机制;也就是说如果类实现了 load 函数就会调用,当类未实现load方法时,不会调用父类load方法。
4. 先编译的分类, 优先调用load,当有多个类别(Category) 都实现了load 方法,这几个load方法都会执行, 执行顺序和分类 在 Compile Sources中出现的顺序一致。
注意:父类Catregory 与子类 Category 的 load 方法执行顺序:由分类在编译器中的编译顺序决定,与其在Compile Sources 出现顺序一致。
5. 先编译的类, 优先调用load,,有多个不同的类的时候, 每个类 load 执行顺序与 在Compile Sources 出现的顺序一致。
6. load方法方法内部使用了锁,是线程安全的。有一定的性能开销会很微弱的影响启动时间,重写方法时要尽可能保持简单,避免阻塞线程,不要再使用锁。
7. 在 load方法中向其它类发送消息,接收消息的类中的+load方法可能尚未被调用。
注意:load调用时机比较早,当load调用时,其他类可能还没加载完成,运行环境不安全.
8. 调用优先级:父类>子类>分类,并且不会被覆盖,均会调用
日常使用场景 :是线程安全的,一定会调用且只调用一次
1. 通常在使用UrlRouter的时候注册类的时候也在+load方法中注册
2. 用来交换方法Method Swizzle
应用:因为类或者分类一旦被加载到运行时,就会调用这个方法;因为加载时间特别早:所以可以利用这个特性进行一些处理
问题:在子类的load 中 [super load] 会调用到哪个类中?
[super load] 将调用到 类最后一个被编译的分类的 load 方法,因为这里是消息发送,而不是通过方法指针。
添加处理category到类的工作会先于类的加载处理 (+load方法的执行)。
在load方法中可以调用category中声明的方法。
initialize 方法
1. 是懒加载,在类或者其子类第一次使用时调用。调用顺序在 main 方法之后,实例化对象之前。
注意:即使类文件被引用进项目,如果一直没有使用这个类,方法不会被执行。
2. 理论上类的 initialize 方法 只会调用一次
注意: initialize 方法是通过 objc_msgSend 调用的,经过方法查找和消息转发的过程。如果子类没有实现 initialize,就会调用父类的 initialize 方法,因此父类 initialize 方法 存在多次调用的可能。
例子:子类不实现initialize方法,会调用父类的。父类初始化时, 已经调用过一次自己initialize方法. 父类initialize 方法可能执行2次
使用场景:在initialize 方法中使用hook,做方法替换就可能会出现多次替换,导致方法替换失效。可以使用类型判断避免这种问题。
3. 父类调用一定在子类之前。初始化子类时,系统会默认初始化父类先。
🐳🐳🐳:如果先引用父类的实例对象,再引用子类实例对象,则会在引用父类实例对象时调用父类 initialize 方法;用子类实例对象时,由于父类的 initialize 方法已经执行,所以此时只调用子类 initialize 方法。
⚠️⚠️:初始化自己之前,递归执行父类的初始化操作;先判断父类有没有初始化initialize 完成,如果没有则递归执行父类的初始化,父类的initialize 调用 > 子类initialize 调用。
4. 分类的 initialize 方法会覆盖原类的 initialize。(Person、或 Person+Category)都是一个Perosn类,只会执行分类的initialize, 原类的initialize 不再执行。先初始化分类, 后初始化子类。
注意: 如有多个分类,只执行最后被编译的分类的 initialize 方法,其他分类的 initialize 方法都会失效;生效的是在 Compile Sources列表中最后一个 Category。
5. initialize 方法内部使用了锁,是线程安全的,可能会阻塞线程,方法中应做一些简单不复杂的类初始化的前期准备工作。
注意:initialize方法中运行环境基本健全。,initialize方法一般用于初始化全局变量或静态变量。
6. 调用优先级:分类>父类,父类>子类。
如果分类和父类均实现了+initialize,则只有分类的+initialize会被调用;
如果父类和子类均实现了+initialize,第一次引用 子类时,先调用父类的+initialize,再调用子类的+initialize;
如果子类未实现initialize 方法,父类实现了+initialize,则第一次引用子类时,会调用两次父类的+initialize
其他:
realizeClass:realizeClass 处理后的类才是『真正的』类,调用时不能对类做写操作。
类初始化之前,objc_class->data() 返回的指针指向 class_ro_t 结构体。等 static Class realizeClass(Class cls) 静态方法在类第一次初始化时被调用,它会开辟 class_rw_t 的空间,并将 class_ro_t 指针赋值给 class_rw_t->ro。
懒加载类优点:1. 加快启动速度 2. 节省应用初始内存
1. 如果一个类实现了 + load 方法,这个类是 non-lazy class(非懒加载类)。如果所有的类都在启动的时候调用load方法,就会非常慢。
2. 没实现 + load 方法,是懒加载类。等到调用的时候再去实现,可以加快启动速度。
每个类都有很多的代码,包括变量,方法等,会占用很多内存,如果这个类在工程中就没调用,或者在很深的页面才会调用,正常情况下很少会使用到,如果在启动时加载了,就会造成内存浪费。
getter 也可使用懒加载方式
在dyld调用静态构造函数之前,libc 会调用 _objc_init()
objc_init会调用map_images 方法中-- > 会调用 read_images方法
read_images 中会处理分类,把分类加到类上(方法,属性等),然后进行类的加载处理调用 load 方法等操作。
参考: