一. +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
方法,第三步算是处女座的处理,处理异常。
3. 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_method)(cls, SEL_load);
这段代码也就是说+load
方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend
来发送消息.
也正是这句代码造就了+load
方法的最大特点:类,父类与分类之间+load
方法的调用是互不影响的.也就是,子类不会主动调用父类的+load
方法,如果类与分类都实现了+load‘,那么两个
+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; 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!"); } }
_class_initialize
方法实现看起来比较长,但其实关键步骤也就只有两步:
- 确保当前类的父类
supercls
已经初始化完成 -- 如果没有则通过_class_initialize(supercls)
重新进入_class_initialize
方法,初始化父类。 - 处理当前类的初始化状态。
第一步比较简单,不再赘述,只针对第二个步骤作分析.
状态处理
当进入第二步,会首先根据当前类的初始化状态决定是否要发送初始化消息.
- 未初始化
1) 如果当前类未初始化,则会向它发送一个setInitializing
消息,将该类的元类的信息更改为CLS_INITIALIZING
,并通过reallyInitialize
标识来与Initializing
区分.
2) 成功设置CLS_INITIALIZING
后,_setThisThreadIsInitializingClass
记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待.
3) 通过callInitialize
调用initialize
方法: -
void callInitialize(Class cls) { ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); asm(""); }
- 也就是说与
+load
不同该方法是通过objc_msgSend
发送消息实现的,因此也拥有objc_msgSend
带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类. - 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流程图
总得来说:
1.+initialize
方法是在main函数之后调用的;
2.+initialize
方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;
3.子类中不需要调用super方法,会自动调用父类的方法实现;
4.+initialize
只调用一次,init
可多次调用.
三. + load与+ initialize的异同
更多:每周更新关注新浪微博!iOS开发者交流群:446310206