objc源码下载地址
寻找类的load方法不是走的消息发送机制,故不会出现消息覆盖现象
- (void)load
void _objc_init(void)
{
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
调用load_images
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
//互斥递归锁 Class,来解决多次上锁而不会发生死锁的问题,其作用与 NSRecursiveLock 相同,但不是由 NSLock 再封装,而是通过 C 为 Runtime 的使用场景而写的一个 Class。
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在所有的images映射到内存之后,开始加载load方法
- prepare_load_methods:准备调用(类的load 方法和分类的load方法两种)
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//拿到所有的类信息(类信息不止是在类当中有,分类当中也会有load方法)
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//类当中的load方法
schedule_class_load(remapClass(classlist[i]));// 通过 remapClass 获取类指针
}
//取出所有分类当中的load方法
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
}
1.1schedule_class_load:类的load 方法
如果一个类实现了 +load 方法,那么它就是个 NonlazyClass
1.1.1 拿到所有加载进去的类的列表
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
1.1.2. 开始遍历 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;
// 递归到深层(超类)运行,总是能够保证没有调用 load 方法的父类先于子类加入 loadable_classes 数组,从而确保其调用顺序的正确性。
schedule_class_load(cls->superclass);//superclass父类
// 将需要执行 load 的 Class 添加到一个全局列表中
add_class_to_loadable_list(cls);
// 标记 RW_LOADED 符号
cls->setInfo(RW_LOADED);
}
1.1.3. schedule_class_load(cls->superclass);递归的方式调用父类的load方法
1.1.4. add_class_to_loadable_list(cls);把这个类的load方法驾到list中
1.2 category的方法的准备
_getObjc2NonlazyClassList:获取所有拥有 load 方法的类(class)
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
从 section 中获取类列表,并对每个 class 进行 remap 以及 realize 处理
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
// 通过 remapClass 获取 Category 对象存有的 Class 对象
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
// 对类进行第一次初始化,主要用来分配可读写数据空间并返回真正的类结构
realizeClass(cls);
assert(cls->ISA()->isRealized());
// 将需要执行 load 的 Category 添加到一个全局列表中
add_category_to_loadable_list(cat);
}
_getObjc2NonlazyCategoryList:获取所有拥有 load 方法的 分类(category)
remapClass:通过 remapClass 获取类指针
none-lazy class 是一个很特殊的区中取出来的 class 列表,只有 load 方法实现的类中才提前设置它的一些属性,否则,只加载最基本的数据即可。
realizeClass:如果类中没有 +load 方法,那么 realize() 返回的就是 false,否则为空。并且另外的一些相关属性也会有所变化,比如: hasCxxCtor & hasCustomAWZ
add_category_to_loadable_list:放入到静态变量数组 以及 loadable_categories
1.3call_load_methods:调用
void call_load_methods(void)
{
static bool loading = NO;// 是否已经录入
bool more_categories;// 是否有关联的 Category
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return; // 由于 loading 是全局静态布尔量,如果已经录入方法则直接退出
loading = YES;
// 声明一个 autoreleasePool 对象
// 使用 push 操作其目的是为了创建一个新的 autoreleasePool 对象
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,调用 Category 中的 load 方法,这个只要调用一次,毕竟category没有父类这东西
//这里可以在一定程度上确保类的 load 方法会先于分类调用。
//但是这里不能完全保证调用顺序的正确。如果分类的镜像在类的镜像之前加载到运行时,这里的代码就没法保证顺序的正确了
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories,继续调用,直到所有 Class 全部完成
} while (loadable_classes_used > 0 || more_categories);
// 将创建的 autoreleasePool 对象释放
objc_autoreleasePoolPop(pool);
// 更改全局标记,表示已经录入
loading = NO;
}
do while 分为两步:
class的load调用:call_class_loads();->static void call_class_loads(void)
categories的调用: more_categories = call_category_loads();->static bool call_category_loads(void)
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, SEL_load);
cats[i].cat = nil;
}
拿到method。直接函数指针调用函数 (*load_method)(cls, SEL_load);
因为系统执行load方法时不是走消息发送机制,故不存在分类load方法覆盖类load方法的现象,而代码手动调用类的load方法,因为是走消息发送机制,分类load方法会优先执行.
总结一下
load 方法的调用情况至此已经全部清晰。思路梳理如下三大流程:
- Load Images: 通过 dyld 载入 image 文件,引入 Class。
- Prepare Load Methods: 准备 load 方法。过滤无效类、无效方法,将 load 方法指针和所属 Class 指针收集至全局 Class 存储线性表 loadable_classes 中,其中会涉及到自动扩展空间和父类优先的递归调用问题。
- Call Load Methods: 根据收集到的函数指针,对 load 方法进行动态调用。进一步过滤无效方法,并记录 log 日志。
load的调用特点:
• 当类被导入到项目的时候就会执行load函数,
• 在main函数开始执行之前的,这个类是否被用到无关
• 每个类的load函数只会自动调用一次
• load函数是系统自动加载的,不需要调用父类的load函数
• 父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类。
• 子类未实现load方法时,不会调用父类load方法
• 类中的load方法执行顺序要优先于类别(Category)
•有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
网友评论