1. load方法
1.1 load基本用法
我们来看一下load基本使用示例
#import <Foundation/Foundation.h>
@interface People : NSObject
@end
#import "People.h"
@implementation People
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Speak)
@end
#import "People+Speak.h"
@implementation People (Speak)
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface People (Eat)
@end
#import "People+Eat.h"
@implementation People (Eat)
+ (void)load {
NSLog(@"%s",__func__);
}
@end
#import "People.h"
@interface Student : People
@end
#import "Student.h"
@implementation Student
+ (void)load
{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
//#import "People.h"
//#import "People+Speak.h"
//#import "People+Eat.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// People *people = [[People alloc] init];
}
return 0;
}
我们可以发现,仅仅实现了load方法,即使没有使用到类,只要编译完运行,就会调用load方法。为什么呢,接下来我们看一下
1.2 load调用原理
+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
//跟进load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
//跟进call_load_methods();
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1\. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2\. Call category +loads ONCE
more_categories = call_category_loads();
// 3\. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
因为在程序运行时就调用,所以我们也是从runtime初始化方法开始,load调用顺序如下
1.3 调用顺序
-
先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
-
再调用分类的+load
- 按照编译先后顺序调用(先编译,先调用)
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1\. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) { //先调用类的+load
call_class_loads();
}
// 2\. Call category +loads ONCE
more_categories = call_category_loads(); //再调用分类的+load
// 3\. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// 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; //取出classes的method
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load); //直接调用
}
// Destroy the detached list.
if (classes) free(classes);
}
Category 和 Class 的 + load 方法的调用顺序规则总结如下:
- 先调用主类,按照编译顺序,顺序地根据继承关系由父类向子类调用;
- 调用完主类,再调用分类,按照编译顺序,依次调用;
- load 方法除非主动调用,否则只会调用一次。
通过这样的调用规则,我们可以知道:主类的 + load 方法调用一定在分类 + load 方法调用之前。但是分类 + load 方法调用顺序并不是按照继承关系调用的,而是依照编译顺序确定的,这也导致了 + load 方法的调用顺序并不一定确定。一个顺序可能是:
父类 -> 子类 -> 父类分类 -> 子类分类
也可能是
父类 -> 子类 -> 子类分类 -> 父类分类
所以student比DGPerson+test先打印,但是不管怎样最先打印的都是person,并且 Load
方法的调用是早于 main
函数的,如下所示:
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用的,下面我们再看一下initialize方法,如果通过这种方式 [people load]
手动调用load,那就会走消息发送机制。
+initialize方法
- +initialize 方法会在类第一次接收到消息时调用,即通过objc_msgSend进行调用,当一个类在查找方法的时候,会先判断当前类是否初始化,如果没有初始化就会去调用initialize方法。
- 调用顺序
- 先调用父类的 +initialize 方法,再调用子类的 +initialize 方法
- +initilize 和 +load 的最大区别是 +initilize 是通过 objc_msgSend 进行调用的,所以有以下特点:
- 如果子类没有实现 +initilize 方法,会调用父类的 +initilize 方法(所以父类的 +initilize 可能会被调用多次)
- 如果分类实现了 +initilize 方法,就覆盖类本身的 +initilize 调用。
- 作用:初始化操作
可以自己写代码举例证明,这里只是通过runtime源码,去了解一下。
首先,在runtime源码中,我们创建了一个Person类,在Person类的initalize方法中进行打印,并且加上断点:
这里直接运行是没有任何输出的,因为上面有提到initialize的调用时机,initialize在类第一次接收到消息时调用,需要在main.m 文件中,调用Person 的class方法:
此时,我们会看到走到了断点处:
从上图中我们可以看到,main函数执行之后的流程是:
- _objc_msgSend_uncached
- _class_lookupMethodAndLoadCache3
- lookUpImpOrForward
- _class_initialize
- callInitialize
- [Person initialize]
我们直接从第3步看就行了,下面是lookUpImpOrForward的方法代码:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
... // 判断这个类是否初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
...
}
cls->isInitialized() 其实就是一个标记这个类是否初始化的标志:
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
此时我们继续看 _class_initialize 这个方法:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// 先调用父类的,再调用子类的
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
// 递归调用
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
#if __OBJC2__
@try
#endif
{
// 调用 cls 的initalize 方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
return;
}
else {
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
继续看callInitialize 方法:
void callInitialize(Class cls)
{
// 消息查找流程
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
所以到此非常明了,从源码中我们可以看到
- 先调用父类的initialize方法,再调用子类的initalize方法,由于initalize方法会走消息查找流程,所以当分类中也实现了initialize方法之后,只会执行分类的initalize方法。
- 如果本类没有实现initialize方法,父类实现了initialize方法,则多个子类初始化时会多次调用父类的initialize方法,但是本质上只有第一次initialize方法是初始化父类,后面几个initialize都是方法的调用,即子类没有实现,通过superclass到父类里查找。
完整的+initialize
调用流程可参考下图:
+load和+initialize例子:demo下载地址
网友评论