主要讲解 category 和+(void)load 的关系;
isa和 superclass 的指向关系, 请先看这篇文章, 不然下面很多知识可能会无法理解;
分类(Category)部分一
分类(Category)部分二
分类(Category)部分三
+(void)load和+(void)initialize 区别总结
1. 分类是否有+(void)load
方法? 如果有, 分类的会不会覆盖类的+(void)load
方法?
分类有+(void)load
方法, 且不会覆盖类的+(void)load
方法;
下面讲解下+(void)load
的调用过程:
+(void)load
方法会在runtime
编译加载类和分类时调用, 通过函数指针的方式直接调用, 并不是通过objc_msgSend()
的方式调用(此方式需要通过 isa
和superclass
来查找方法);
-
+(void)load
会在runtime
加载类和分类时调用; - 每个类/分类的
+(void)load
方法只会调用一次; - 调用顺序多个类按照编译的顺序先后调用
+(void)load
方法; - 调用子类的
+(void)load
方法时会先调用父类的+(void)load
方法; - 最后按照编译的顺序调用分类的
+(void)load
方法;
代码验证:
///Person继承 NSObject
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
+ (void)load {
NSLog(@"Person +load 调用");
}
@end
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Man : Person
@end
NS_ASSUME_NONNULL_END
///Man 继承 Person
#import "Man.h"
@implementation Man
+ (void)load {
NSLog(@"Person 的子类 Man 的 +load 方法");
}
@end
///NSObject的分类 AAA
#import "NSObject+AAA.h"
@implementation NSObject (AAA)
+ (void)load {
NSLog(@"NSObject (AAA) +load 调用");
}
@end
///NSObject的分类 BBB
#import "NSObject+BBB.h"
@implementation NSObject (BBB)
+ (void)load {
NSLog(@"NSObject (BBB) +load 调用");
}
@end
///Man 的分类 CCC
#import "Man+CCC.h"
@implementation Man (CCC)
+ (void)load {
NSLog(@"Man (CCC) +load 方法");
}
@end
2020-06-22 16:22:55.027773+0800 Category 分类补充[18306:244348] Person +load 调用
2020-06-22 16:22:55.028488+0800 Category 分类补充[18306:244348] Person 的子类 Man 的 +load 方法
2020-06-22 16:22:55.028625+0800 Category 分类补充[18306:244348] Man (CCC) +load 方法
2020-06-22 16:22:55.028720+0800 Category 分类补充[18306:244348] NSObject (BBB) +load 调用
2020-06-22 16:22:55.028816+0800 Category 分类补充[18306:244348] NSObject (AAA) +load 调用
objc4源码分析为什么类的+load 优先于分类的+load原因
///runtime 入口
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
///load_images加载模块
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
===>
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// 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
{
mutex_locker_t lock2(runtimeLock);
///准备方法, 通过此方法可以确定为什么会先调用父类的+load 方法, 此流程只
///讲解为什么类的+load 方法会优先于分类的 category, 为什么父类优先想看下面的流程;
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
///调用 +load 方法;
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
/*
注意这个注释, 循环重复的调用+load 方法, 直到不再有+load 方法;
将所有类的+load 方法调用完后才会调用 category 的+load 方法;
*/
while (loadable_classes_used > 0) {
///调用 class 的+load方法
call_class_loads();
}
// 2. Call category +loads ONCE
///调用 category 的+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 方法
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;
/*
调用方法, classes[i]取出一个类, 然后调用其.method; 就是这个类的+load 方法实现;
struct loadable_class {
Class cls; // may be nil
///类的+load 方法
IMP method;
};
*/
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, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
===>
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
/*
调用方法, cats[i]取出一个 分类, 然后调用其.method; 就是这个分类的+load 方法实现;
struct loadable_category {
Category cat; // may be nil
///分类的+load 方法实现
IMP method;
};
*/
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, @selector(load));
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
...
}
objc4源码分析为什么父类类的+load会优先调用;
///接着上个流程中准备方法的方法查看
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
///循环遍历各个类, 然后规划类的 load 方法;
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
===>
static void schedule_class_load(Class cls)
{
///基类(NSObject )的superclass 为 nil, 则递归结束
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
/*
看这句官方注释所说; 确保顺序是: 确保父类优先; schedule_class_load递归调用,
一直传入 class 的superclass 指针, 直到查找到基类的superclass为止;
*/
schedule_class_load(cls->superclass);
///把将class 加入到一个数组loadable_list中准备好;
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
===>
至此: 可以解释为什么会先调用父类的+(void)load 方法;
补充:
1. 如何更改分类的编译顺序?
如图所示, 哪个分类在前面则先编译;
2. 具体实例调用[Man load]
打印出什么?
打印结果为Man (CCC) +load 方法
;
#import "Man.h"
@implementation Man
+ (void)load {
NSLog(@"Person 的子类 Man 的 +load 方法");
}
@end
#import "Man+CCC.h"
@implementation Man (CCC)
+ (void)load {
NSLog(@"Man (CCC) +load 方法");
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
[Man load];
}
2020-06-22 16:24:40.694917+0800 Category 分类补充[18306:244348] Man (CCC) +load 方法
此处直接调用 load 方法, 最终转换为 objc_msgSend的方式;
则查找load 方法顺序为, 首先查找元类中方法的列表. 由于分类的如果有方法跟类的方法同名,
则是优先调用分类的方法, 所以这个地方最终会调用分类的+load, 而不是 Man 的+load;
#import "Man+CCC.h"
@implementation Man (CCC)
+ (void)load {
NSLog(@"Man (CCC) +load 方法");
}
@end
参考: 调用方法的顺序
通过指令将OC文件转换为C++文件
指令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件.m -o 文件-arm64.cpp
如果需要链接其他框架, 使用-framework参数; 例: -framework UIKit
参考文章和下载链接
测试代码
Apple 一些源码的下载地址
有理解错误地方请不吝赐教