通过上一篇的介绍我们知道了App
的启动流程中dyld
做了哪些事情,那么:
+load
和+initialize
分别是什么时候调用的呢?以及他们在父类,子类,分类中调用的顺序是什么样的呢?
我们先准备如下几个Class
和Category
:
Class-NXPerson
代码:
@interface NXPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)run;
@end
@implementation NXPerson
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)run{
NSLog(@"%s",__func__);
}
@end
Class-NXTeacher
代码:
@interface NXTeacher : NXPerson
- (void)teach;
@end
@implementation NXTeacher
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)teach{
NSLog(@"%s",__func__);
}
@end
Class-NXStudent
代码:
@interface NXStudent : NXPerson
- (void)study;
@end
@implementation NXStudent
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)study{
NSLog(@"%s",__func__);
}
@end
Category-NXPerson(Category)
代码:
@interface NXPerson(Category)
@end
@implementation NXPerson(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)run {
NSLog(@"%s",__func__);
}
@end
Category-NXTeacher(Category)
代码:
@interface NXTeacher(Category)
@end
@implementation NXTeacher(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)teach {
NSLog(@"%s",__func__);
}
@end
Category-NXStudent(Category)
代码:
@interface NXStudent(Category)
@end
@implementation NXStudent(Category)
+ (void)load {
NSLog(@"%s",__func__);
}
+ (void)initialize {
NSLog(@"%s",__func__);
}
- (void)study {
NSLog(@"%s",__func__);
}
@end
代码如上,一共三个类
NXPerson
、NXTeacher
、NXStudent
,后2者继承于前者;三个分类NXPerson(Category)
、NXTeacher(Category)
、NXStudent(Category)
。每个主类分别实现了-run
、-teach
、-study
和+load
、+initialize
方法。每个分类中也实现了主类的方法和+load
、+initialize
方法。
1.+load
方法
首先我们在没有任何调用的情况下的运行代码:
探索:
1.Class
和Category
的+load
方法都被调用了,+initialize
方法没有被调用。
2.Class
是不是一定在Category
前面打印呢?我们将NXPerson(Category)
放在NXStudent
和NXTeacher
之前,继续运行代码发现Class
的+load
依旧比Category
的+load
更先调用。
3.我们更换几个Class
的顺序,发现NXPerson
的+load
永远在最前,同级子类加载的顺序跟代码的顺序有关。至此我们可以推断父类的+load
先于子类调用。
4.我们更换几个Category
的顺序,发现谁的代码在前谁先打印,即分类的+load
方法加载的顺序是根据代码读取的顺序来决定的。
以上结论其实是可以在objc源码中找到答案的:在call_load_methods
中我们也可以看到call_class_loads
先于call_category_loads
调用(源码如下),这个决定了所有的父类的+load
更先读取。
void call_load_methods(void){
...
//调用loadable_classes中`loadable_class`绑定的`load`方法
call_class_loads();
//调用loadable_categories中`loadable_category`绑定的`load`方法
call_category_loads();
....
}
2.initialize方法
我们添加如下代码到main
中:
NXTeacher *p = [[NXTeacher alloc] init];
打印结果如下:
[NXPerson(Category) initialize]
[NXTeacher(Category) initialize]
反复运行打印的只有分类的+initialize
,以及分类对应类的父类的分类的+initialize
,并且父类先于之类调用.
探索:
1.+initialize
方法默认会在main之后,第一次访问该类的时候调用。通常首次访问的方法+alloc
、+self
、+class
、+new
等等类方法。
2.initialize
会先调用父类,再调用子类。如果同级有分类,则分类的方法会覆盖掉主类的方法,多个分类的会调用最后一个加载进内存的分类的方法。
上面我们讲到最先访问到一个类的方式通常是+alloc
、+self
、+class
、+new
方法,接下来我们就初始化一个[NXTeacher alloc]
,而Objective-C
的方法调用,底层都是通过消息转发objc_msgSend
实现的,那么[NXTeacher alloc]
会被转化为objc_msgSend(NXTeacher, @selector(alloc))
来进入汇编代码实现的快速查找流程从缓存中查找,那么第一次调用缓存中肯定没有alloc
方法的;缓存中没有找到,则会进入慢速查找流程lookUpImpOrForward
:
NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
...
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
...
}
static Class realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize){
if (slowpath(!cls->isRealized())) {
/*实现该类*/
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath(initialize && !cls->isInitialized())) {
/*初始化该类*/
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
return cls;
}
2.1.分类方法是怎么添加的?
static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) {
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked){
...
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
realizeClassWithoutSwift(cls, nil);
}
else {
cls = realizeSwiftClass(cls);
}
return cls;
}
static Class realizeClassWithoutSwift(Class cls, Class previously){
...
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
...
// Attach categories
methodizeClass(cls, previously);
return cls;
}
static void methodizeClass(Class cls, Class previously){
...
// Attach categories
if (previously) {
if (isMeta) {
//类方法添加到元类上
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
//实例方法添加到类上
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
void attachToClass(Class cls, Class previously, int flags){
...
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
}
else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags){
...
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
...//property_list_t, protocol_list_t
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
...
}
...
}
//这个方法里边在插入的时候
void attachLists(List* const * addedLists, uint32_t addedCount) {
...
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
/*第1个for循环相当于将老数据往后平移了addedCount个位置*/
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[I];
/*第2个for循环相当于将新数据按顺序放在前面*/
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[I];
free(array());
setArray(newArray);
validate();
...
}
如此以来按顺序查找的时候,那么排在前面的category
方法会被优先调用,categoey-initialze
会被调用,而主类没有被调用的真正原因。
2.2.initialize
调用顺序
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked){
...
initializeNonMetaClass(nonmeta);
...
}
void initializeNonMetaClass(Class cls){
...
/*重点!!!,这里会先调用父类的initialize方法*/
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
allInitialize(cls);
}
void callInitialize(Class cls){
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
}
通过以上的流程,我们可以看出initialize
方法的调用时先调用父类,再调用当前类的。
总结:
一、关于load
方法:
1.class
和category
的+load
方法都会被调用,是在main
之前被调用。
2.调用顺序先主类的+load
,再调用分类的+load
。主类的调用顺序为先superclass
,再subclass
。分类的+load
是根据代码读取的顺序。子类的+load
可能会在前面。
二、关于initialize
方法:
1.如果有分类,那么分类的方法列表会加载进主类的方法列表中,并且放在方法列表的头部,按照代码的读取顺序,最后读取到分类方法会放在最前面。按照顺序查找则最后加载进去的方法会被优先查找到(这也就是主类的同名方法会被分类【覆盖掉】的原因)。分类initialize
会被调用,而主类的initialize
不会被调用。
2.调用顺序依然是先superclass
,再subclass
。