Objective-C之Category的底层实现原理
Objective-C的+initialize方法调用原理分析
别人是这么说的
- 调用时机:
+load
方法会在Runtime加载类对象(class
)和分类(category
)的时候调用 - 调用频率: 每个类对象、分类的
+load
方法,在工程的整个生命周期中只调用一次 - 调用顺序:
先调用类对象(
class
)的+load
方法:- 类对象的
load
调用顺序是按照 类文件的编译顺序 进行先后调用; - 调用子类
+load
之前会先调用父类的+load
方法
再调用分类(
category
)的+load方法:按照编译先后顺序调用(先编译的,先被调用) - 类对象的
一、load方法的调用时机和调用频率
+load
方法是在程序一启动运行,加载镜像中的类对象(class
)和分类(category
)的时候就会调用,只会调用一次,不论在项目中有没有用到该类对象或者该分类,他们统统都会先被加载进内存,因为类的加载只有一次,所以所有的load方法肯定都会被调用而且只有一次。下面先上一个小demo调试看看:
上图里面,我创建了一个person
类,以及它的两个分类–CLPerson+Test/CLPerson+Test2
,然后给它们都加上两个类方法(+load/+test
),main.h
里面先不加任何代码跑跑看。
从日志看出,虽然整个工程都没有import过CLPerson
以及它的两个分类,但是他们的load
方法还是被调用了,并且都发生在main
函数开始之前,而且+test
并没有被调用。所以该现象间接证明了,load
方法的调用应该和类对象以及分类的加载有关。
在main.h
里面调一下+test
方法
接下来通过源码分析一下(Runtime源码下载地址)
首先,进入Runtime的初始化文件objc-os.mm
,找到_objc_init
函数,该函数可以看作是Runtime的初始化函数。
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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);
}
忽略一些与本文主题关联不太的函数,直接看最后句_dyld_objc_notify_register(&map_images, load_images, unmap_image);
其中很明显,load_images
就是加载镜像/加载模块的意思,应该是与我们话题相关的参数,点进去看看它的实现
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
苹果对该函数官方给出的注释是,处理那些正在进行映射的镜像(images)的+load方法。该方法的实现里面,做了两件事情:
prepare_load_methods
// Discover load methods – 查找并准备load方法,以供后面去调用call_load_methods();
//Call +load methods – 调用这些load方法
针对上面案例日志中出现的现象,先从结果出发,逆向分析,来看看load方法是如何调用的,进入call_load_methods();
的实现
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
调用所有的处理中的class和category的+load方法;
* Class +load methods are called superclass-first.
class的+load方法会被先调用,并且,一个调用一个class的+load方法前,会先对其父类的+load进行调用
* Category +load methods are not called until after the parent class's +load.
category的+load方法的调用,会发生在所有的class的+load方法完成调用之后。
*
* Th