(一)load方法
- 类及其分类都有
load
方法,+load方法会在runtime加载类、分类时调用 - 每个类、分类的+load,在程序运行过程中只调用一次
(1)调用顺序
- 先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
- 再调用分类的+load
- 按照编译先后顺序调用(先编译,先调用)
(2)如何查看源码证明调用顺序?
源码解读顺序:
(1) objc-os.mm
文件
_objc_init
load_images
(2) objc-runtime-new.mm文件
load_images
-
load_images_nolock
寻找\整理load方法 -
call_load_methods
调用load方法
(3)objc-loadmethod.mm文件
-
call_load_methods
方法 中 依次调用call_class_loads
和call_category_loads
- 查看这两个方法 即直接依次获取元类、分类的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
// 加载元类对象load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
//加载categoty的load方法
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_class_loads
和call_category_loads
,下面只列举call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
//这个classes为前面`schedule_class_load`方法通过"整理"出来的数组
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方法
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
//调用load方法
if (classes) free(classes);
}
(3)load方法与自定义方法的区别
看到这里也大概有点疑惑,为什么分类的自定义方法会“掩盖”类的原方法,而load不会?
- +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
- 自定义方法是经过objc_msgSend函数,进行一个消息查找的过程
(4)不同的类load方法调用顺序
上面只搞清楚了类与分类之间的调用顺序,那么不同的类(可能有继承关系)调用顺序又是什么样的呢?
回到前面的方法load_images
,在调用call_load_methods
方法前,调用了一个load_images_nolock
方法,里面有一个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]));
}
//分类调用顺序 类的load调用完毕再调用分类,先编译先调用
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);
}
}
我们再看其中的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
//递归调用(优先寻找其父类的load方法)
schedule_class_load(cls->superclass);
//将该类添加到loadable_list中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
这个loadable_list
是在后面call_class_loads
调用时用到的,即可说明:
- 不同的类之间的load方法调用,按编译顺序先后
- 即使子类最初的编译顺序在前,也会优先调用父类的load方法
(默认按照编译顺序,但是要将父类优先load) - 分类是按照编译顺序依次调用
现在我们也能回答前面的面试题了:
4. Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
答:有load方法;load方法是在runtime加载类、分类的时候调用一次;load方法可以继承的,但是load方法一般是系统在调用,一般不会主动调用
(二)initialize方法
-
+initialize
方法会在类第一次接收到消息时调用(第一次alloc)
(1)调用顺序
- 先调用父类的
+initialize
,再调用子类的+initialize
- 先初始化父类,再初始化子类,每个类只会初始化1次
(1)如何查看源码证明调用顺序?
源码解读顺序:
(1) objc-msg-arm64.s
文件
objc_msgSend
(2) objc-runtime-new.mm文件
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
(2)+initialize和+load的区别
-
+initialize
是通过objc_msgSend
进行调用的,所以有以下特点 - 如果子类没有实现
+initialize
,会调用父类的+initialize
(所以父类的+initialize
可能会被调用多次,但不代表被初始化多次,只是objc_msgSend调用了父类的initialize方法) - 如果分类实现了
+initialize
,就覆盖类本身的+initialize
调用
类方法调用:(还是会调用class_getInstanceMethod)
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);//将自身传进去
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
Method meth;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
if (!cls->isRealized()) {
rwlock_writer_t lock(runtimeLock);
realizeClass(cls);
}
//判断是否需要初始化,且还没有被初始化过
if (initialize && !cls->isInitialized()) {
//(第一次的时候才会执行)
_class_initialize (_class_getNonMetaClass(cls, inst));
// 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
}
....//代码省略
return imp;
}
我们再看_class_initialize
方法:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
BOOL reallyInitialize = NO;
//重点: initialize之前需要保证父类已initialized
// 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());
}
//重点:通过objc_msgSend方法调用initialize方法
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
cls->nameForLogging());
}
// 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;
}
...//代码省略
}
因此,通过该方法可以看出, 此处是会先调用父类的initalize,再调用子类的initalize
load和initialize方法的区别是什么?
- 调用方式
- load根据函数地址直接调用
- initialize是通过objc_msgSend调用
2.调用时刻 - load是runtime加载类、分类的时候调用
- initialize是类第一次接受到消息时调用,且每个类只会被初始化一次(父类的initialize可能会调用多次,子类可能并未实现initialize方法)
3.调用顺序 - load方法,①先调用类的load,先编译的类优先调用load,调用子类的load之前会调用父类的load ②其次调用分类的load,先编译优先调用
- initialize 先初始化父类,再初始化子类(子类可能因为未实现initialize,通过objc_msgSend调用父类方法)
ps: 上一期的遗留,同时思考load与initalize方法调用顺序,即原理