也谈load与initialize
- 2017-02-10 14:41 809
一. +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
方法。
1. 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);
}
}
在prepare_load_methods
方法中,分为两个步骤:
一是,获取了所有类后,遍历列表,将其中有+load
方法的类加入loadable_class
;
二是,获取所有的类别,遍历列表,将其中有+load
方法的类加入loadable_categories
.
另外值得注意的一点是schedule_class_load
方法的实现:
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);
}
在该方法中会首先通过schedule_class_load(cls->superclass)
确保父类中的 +load
方法被加入loadable_class
(如果父类有+load
方法的话),从而保证父类的+load
方法总是在子类之前调用。
也因此,在覆写+load
方法时,不需要调用super方法。
2. 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
方法,第三步算是处女座的处理,处理异常。
小结
总得来说:
-
+load
方法是在main函数之前调用的; - 遵从先父类后子类,先本类后列类别的顺序调用;
- 子类中不需要调用super方法,也不会调用父类的
+load
方法实现; - 无论该类是否接收消息,都会调用
+load
方法;
二. initialize
源码分析
在NSObject文件中,initialize
的实现如下..
+ (void)initialize {
}
然后找到了class_initialize
方法,注释表明,当调用class_initialize
方法时,就会给当前未初始化的类发送一条 +initialize
消息。就是它了。
通过查看caller,我们会看到熟悉的lookUpImpOrForward
,也就是消息转发
/***********************************************************************
* 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;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
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);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
@try {
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
cls->nameForLogging());
}
}
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: +[%s initialize] threw an exception",
cls->nameForLogging());
}
@throw;
}
@finally {
// Done initializing.
// If the superclass is also done initializing, then update
// the info bits and notify waiting threads.
// If not, update them later. (This can happen if this +initialize
// was itself triggered from inside a superclass +initialize.)
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else {
waitForInitializeToComplete(cls);
return;
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
_class_initialize
方法实现看起来比较长,但其实关键步骤也就只有两步:
-
确保当前类的父类
supercls
已经初始化完成 -- 如果没有则通过_class_initialize(supercls)
重新进入_class_initialize
方法,初始化父类。 -
处理当前类的初始化状态。
第一步比较简单,不再赘述,只针对第二个步骤作分析.
状态处理
当进入第二步,会首先根据当前类的初始化状态决定是否要发送初始化消息.
- 未初始化
1) 如果当前类未初始化,则会向它发送一个setInitializing
消息,将该类的元类的信息更改为CLS_INITIALIZING
,并通过reallyInitialize
标识来与Initializing
区分.
2) 成功设置CLS_INITIALIZING
后,_setThisThreadIsInitializingClass
记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待.
3) 通过callInitialize
调用initialize
方法.
4) 完成initialize
方法后,更新当前类的状态.如果父类已经完成初始化,则_finishInitializing
立马更新,否则通过_finishInitializingAfter
等父类完成后再更新.
首先来看父类没有完成初始化时的处理 - _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);
}
在该方法中,通过声明一个PendingInitialize
类型的结构体pending
来存储当前类与父类信息,并以父类supercls
为key值,以pending
为value存储在pendingInitializeMap
链表中.
而如果父类完成了初始化则进入_finishInitializing
处理:
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;
}
}
在该方法中会首先将当前类标记为已完成初始化状态Initialized
,然后去读取pendingInitializeMap
,如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化.
-
正在初始化
如果是当前线程在进行初始化,则不做处理.
如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全. -
已完成初始化
如果已经完成初始化,则不做处理.
init
提到初始化方法,不可避免的会想到-init
方法.
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
由源码可以看到,-init
方法并没有次数的限制,这也符合我们之前的认知.
那么+ initialize
与-init
两者的调用顺序又是怎样的呢?
按上文的分析,我推测,由于首先向类发送了alloc
消息,此时会触发+ initialize
,然后才发送init
消息,所以应该是先执行的+ initialize
.
通过demo也证实了这一想法.
小结
总得来说:
-
+initialize
方法是在main函数之后调用的; - 子类中不需要调用super方法,会自动调用父类的方法实现;
- 该方法遵从懒加载方式,只有当当前类第一次接受消息时会触发,只执行一次.
-
init
可多次调用.
三. + load与+ initialize的异同
同:
- 都只调用一次
- 都不需要调用super方法
异:
-
+load
在main函数之前调用,+initalize
在main函数之后调用 - 只要一个类中存在
+load
,无论该类是否接收消息,+load
方法都会调用;而initialize
只有在该类第一次接收消息时调用. -
+load
不会主动调用父类中的方法,只是当父类存在+load
方法时会首先调用父类,而+ initialize
会继承父类方法并主动调用.