OC基础-category(2)
Category的load方法
定义一个ZKPerson类,然后类扩展出来两个分类(Test)(Test1)如下图
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZKPerson : NSObject
+(void)test;
@end
#import "ZKPerson.h"
@implementation ZKPerson
+ (void)load
{
NSLog(@"ZKPerson - +load");
}
+ (void)test
{
NSLog(@"ZKPerson - +test");
}
@end
Test
#import "ZKPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZKPerson (Test)
+(void)test;
@end
NS_ASSUME_NONNULL_END
#import "ZKPerson+Test.h"
@implementation ZKPerson (Test)
+ (void)load
{
NSLog(@"ZKPerson (Test)- +load");
}
+ (void)test //这个警告的意思是, 在category中重写了原类的方法
{
NSLog(@"ZKPerson(Test) - +test");
}
@end
Test1
#import "ZKPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZKPerson (Test1)
+(void)test;
@end
NS_ASSUME_NONNULL_END
#import "ZKPerson+Test1.h"
@implementation ZKPerson (Test1)
+ (void)load
{
NSLog(@"ZKPerson (Test1)- +load");
}
+ (void)test //这个警告的意思是, 在category中重写了原类的方法
{
NSLog(@"ZKPerson(Test1) - +test");
}
@end
调用 [ZKPerson test];查看打印结果如下
2021-03-04 10:37:18.951342+0800 Test[87457:728941] ZKPerson - +load
2021-03-04 10:37:18.951808+0800 Test[87457:728941] ZKPerson (Test)- +load
2021-03-04 10:37:18.951891+0800 Test[87457:728941] ZKPerson (Test1)- +load
2021-03-04 10:37:18.952038+0800 Test[87457:728941] ZKPerson(Test1) - +test
Program ended with exit code: 0
这里只打印了Test1分类的方法,因为编译时顺序Test1分类比Test分类后编译。但是注意,每个类的+load方法都执行了一次这是为什么?我们查看runtime源码:
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
//然后再调用分类的+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;
}
由源码可知,每个类的+load方法都将调用一次。
我们点进去call_class_loads()方法
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
//数组里面存放所有类
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_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.
if (classes) free(classes);
}
我们看到+load方法的调用跟普通的方法调用不一样,它是直接从类里面拿到这个方法的地址,然后直接调用。所以每个类的+load方法都会调用。
+load方法的调用顺序
由上面源码可知,很显然+load方法的调用顺序是先调用原有类的+load方法,然后再调用分类的+load方法。那么原有类的+load方法调用顺序呢?如果有多个分类其调用顺序又是怎样呢?
先看原有类的+load方法调用顺序
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);
}
从上面schedule_class_load()方法中可以看到会优先调用父类的+load方法。如果没有继承关系,那么就会按照编译的顺序去调用,先编译,先调用。
+load方法调用顺序总结:
1、首先是调用类的+load方法,按类的编译顺序调用(先编译,先调用),优先编译的先调用,调用子类的+load方法之前优先调用父类的+load方法
2、然后调用分类的+load方法,按类的编译顺序调用(先编译,先调用),优先编译的先调用
category中有load方法,且是在runtime加载类,分类的时候被调用
问题:load方法能继承吗?
答案:一般我们是不会主动去调用+load方法的,会等系统调用,一般继承是在我们调用方法的时候才会有意义。(存在于子类和父类之间)。是可以继承的,子类没有,去找父类的load方法
相当于给子类发一个消息机制,objc_msgSend([ZKStudent class] @selector(load)) isa->元类未找到,通过superclass找到父元类里面的类方法