+load
对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次。
iOS会在应用程序启动的时候调用
+load
方法,在main
函数之前调用。执行子类的
+load
方法前,会先执行所有超类的load方法,顺序为父类->子类->分类。在
+load
方法中使用其他类是不安全的,因为会调用其他类的+load
方法,而如果关系复杂的话,就无法判断出各个类的载入顺序,类只有初始化完成后,类实例才能进行正常使用。+load
方法不遵从继承规则,如果类本身没有实现+load
方法,那么系统就不会调用,不管父类有没有实现(跟下文的+initialize
有明显区别)。尽可能的精简
+load
方法,因为整个应用程序在执行+load
方法时会阻塞,即,程序会阻塞直到所有类的+load
方法执行完毕,才会继续。+load
方法中最常用的就是方法交换method swizzling
。
源码浅析
+load
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
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在runtime源码中,
+load
方法是在load_images
中通过call_load_methods
调用的。在运行时加载镜像时,通过
prepare_load_methods
方法将+load
方法准备就绪,而后执行call_load_methods
,调用+load
方法。
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);//获取所有类列表
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
在prepare_load_methods方法中,获取了所有类后,遍历列表,将其中有
+load
方法的类加入loadable_class
。在prepare_load_methods方法中,获取所有的类别后,遍历列表,将其中有
+load
方法的类加入loadable_categories
。在
schedule_class_load
方法中会首先通过schedule_class_load(cls->superclass)
确保父类中的+load
方法被加入loadable_class
(如果父类有+load
方法的话),从而保证父类的+load
方法总是在子类之前调用。也因此,在覆写+load
方法时,不需要调用super
方法。
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;
}
call_load_methods
方法首先调用类的load
方法。在
call_class_loads
方法中通过在第一步读取prepare_load_methods
步骤里的loadable_classes
,遍历列表并调用+load
方法。然后类似的调用类别的
+load
方法。最后处理可能出现的异常。
call_class_loads
static void call_class_loads(void)
{
int i;
//1.获取列表
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
//2.循环调用load方法
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
(*load_method)(cls, SEL_load);
}
// 3. 释放列表
if (classes) free(classes);
}
+load
方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend来发送消息。类,父类与分类之间
+load
方法的调用是互不影响的。子类不会主动调用父类的
+load
方法,如果类与分类都实现了+load',那么两个
+load`方法都会被调用。
+initialize
在首次使用该类之前由运行期系统(非人为)调用,且仅调用一次。
惰性调用,只有当程序使用相关类时,才会调用。
运行期系统会确保
+initialize
方法是在线程安全的环境中执行,即,只有执行+initialize
的那个线程可以操作类或类实例。其他线程都要先阻塞,等待initialize执行完。如果类未实现
+initialize
方法,而其超类实现了,那么会运行超类的实现代码,而且会运行两次(load 第5点)。+initialize
遵循继承规则。初始化子类的的时候会先初始化父类,然后会调用父类的
+initialize
方法,而子类没有覆写+initialize
方法,因此会再次调用父类的实现方法。+initialize
方法也需要尽量精简,一般只应该用来设置内部数据,比如,某个全局状态无法在编译期初始化,可以放在initialize里面。
源码浅析
+ (void)initialize {
}
class_initialize
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
@try {
callInitialize(cls);
}
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: +[%s initialize] threw an exception",
cls->nameForLogging());
}
@throw;
}
@finally {
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else {
waitForInitializeToComplete(cls);
return;
}
}
else if (cls->isInitialized()) {
return;
}
else {
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
确保当前类的父类
supercls
已经初始化完成 -- 如果没有则通过_class_initialize(supercls)
重新进入_class_initialize
方法,初始化父类。根据当前类的初始化状态决定是否要发送初始化消息。如果当前类未初始化,则会向它发送一个
setInitializing
消息,将该类的元类的信息更改为CLS_INITIALIZING
,并通过reallyInitialize标识来与Initializing
区分。_setThisThreadIsInitializingClass
记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待。通过
objc_msgSend
发送消息实现的,因此也拥有objc_msgSend
带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类。完成
initialize
方法后,更新当前类的状态.如果父类已经完成初始化,则_finishInitializing
立马更新,否则通过_finishInitializingAfter
等父类完成后再更新。
static void _finishInitializingAfter(Class cls, Class supercls)
{
PendingInitialize *pending;
classInitLock.assertLocked();
if (PrintInitializing) {
_objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]",
cls->nameForLogging(), supercls->nameForLogging());
}
if (!pendingInitializeMap) {
pendingInitializeMap =
NXCreateMapTable(NXPtrValueMapPrototype, 10);
// fixme pre-size this table for CF/NSObject +initialize
}
pending = (PendingInitialize *)malloc(sizeof(*pending));
pending->subclass = cls;
pending->next = (PendingInitialize *)
NXMapGet(pendingInitializeMap, supercls);
NXMapInsert(pendingInitializeMap, supercls, pending);
}
- 在
_finishInitializingAfter
方法中,通过声明一个PendingInitialize
类型的结构体pending
来存储当前类与父类信息,并以父类supercls
为key
值,以pending
为value
存储在pendingInitializeMap
链表中。
static void _finishInitializing(Class cls, Class supercls)
{
PendingInitialize *pending;
classInitLock.assertLocked();
assert(!supercls || supercls->isInitialized());
if (PrintInitializing) {
_objc_inform("INITIALIZE: %s is fully +initialized",
cls->nameForLogging());
}
// mark this class as fully +initialized
cls->setInitialized();
classInitLock.notifyAll();
_setThisThreadIsNotInitializingClass(cls);
// mark any subclasses that were merely waiting for this class
if (!pendingInitializeMap) return;
pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
if (!pending) return;
NXMapRemove(pendingInitializeMap, cls);
// Destroy the pending table if it's now empty, to save memory.
if (NXCountMapTable(pendingInitializeMap) == 0) {
NXFreeMapTable(pendingInitializeMap);
pendingInitializeMap = nil;
}
while (pending) {
PendingInitialize *next = pending->next;
if (pending->subclass) _finishInitializing(pending->subclass, cls);
free(pending);
pending = next;
}
}
在
_finishInitializing
方法中会首先将当前类标记为已完成初始化状态Initialized
,然后去读取pendingInitializeMap
。如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化。
如果是当前线程在进行初始化,则不做处理。
如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全。
如果已经完成初始化,则不做处理。
- (instancetype)init
-(instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
NS_DESIGNATED_INITIALIZER
#endif
;
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
-(instancetype)init
方法并没有次数的限制。+ initialize
先于-(instancetype)init
执行。
+ load
与+ initialize
的异同
要点
在加载阶段,如果类实现了load方法,那么系统就会调用它。分类里也可以定义此方法,类的load方法要比分类中的先调用。与其他方法不同,load方法不参与覆写机制。
首次使用某个类之前,系统会向其发生initialize消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。
load与initialize方法都应该实现的精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率。
无法在编译期设定的全局常量,可以放在initialize方法里初始化。