1.类的加载流程
我们先梳理下类的加载流程:
-
libObjc 向 dyld 注册了回调 _dyld_objc_notify_register,当 dyld 把 App 以及 App 所依赖的一系列 Mach-O 镜像加载到当前 App 被分配的内存空间之后,dyld 会通过 _dyld_objc_notify_mapped 也就是 map_images 来通知 libObjc 来完成具体的加载工作,map_images 被调用之后会来到 _read_images
-
_read_images
-
主要会进行类的加载工作,会插入所有的类到 gdb_objc_realized_classes 哈希表中(插入方式为 类名为 key,类对象为value, 不包括通过 共享缓存 里面的类),同时还会把类插入到 allocatedClasses 这个集合里面,注意,allocatedClasses 的类型为 NXHashTable,可以类比为 NSSet,而 gdb_objc_realized_classes 的类型为 NXMapTable,可以类比为 NSDictionary
-
对所有的类进行重映射
-
将所有的 SEL 插入到 namedSelectors 哈希表中(插入方式为:SEL 名称为 key,SEL 为value)
-
修复函数指针遗留
-
将所有的 Protocol 插入到 readProtocol 哈希表中(插入方式为:Protocol 名称为 key,Protocol 为 value)
-
对所有的 Protocol 做重映射
-
初始化所有的非懒加载类,包括 rw 和 ro 的初始化操作
-
处理所有的分类(包括类的分类和元类的分类)
-
2.懒加载类的加载流程
我们知道,非懒加载类是指实现了load方法的类,懒加载类是没有实现load方法的类,那么懒价值类是什么时候加载呢,我们推动是调用方法的时候。对此,我们在 lookUpImpOrForward方法里打个断点


static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
assert(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
我们一路跟随断点来到了 realizeClassMaybeSwiftMaybeRelock方法,然后我们看到了我们熟悉的一个方法 realizeClassWithoutSwift,这个方法内部会进行 ro/rw的赋值操作以及 category的 attatch 也就是下面的流程

3.分类的底层实现
为了探究分类的底层实现,我们只需要用 clang 的重写命令
clang -rewrite-objc LGTeacher+test.m -o category.cpp
我们查看 category.cpp 这个文件,来到文件尾部可以看到:
static struct _category_t _OBJC_$_CATEGORY_LGTeacher_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LGTeacher",
0, // &OBJC_CLASS_$_LGTeacher,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGTeacher_$_test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGTeacher_$_test,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGTeacher_$_test,
};
我们找到category_t的源码:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
可以看出:
- name : 是分类所关联的类,也就是类的名字,而不是分类的名字
- cls : 我们在前面可以看到 clang 重写后这个值为 0,但是后面有注释为 &OBJC_CLASS_$_LGTeacher ,也就是我们的类对象的定义,所以这里其实就是我们要扩展的类对象,只是在编译期这个值并不存在
- instanceMethods : 分类上存储的实例方法
- classMethods :分类上存储的类方法
- protocols :分类所实现的协议
- instanceProperties:分类所定义的实例属性,不过我们一般在分类中添加属性都是通过关联对象来实现的
4.分类的加载
4.1 分类没有实现load方法
4.1.1 与懒加载类配和加载
我们先分析第一种情况,也就是类和分类都不实现 load方法的情况。首先,非懒加载类的流程上面我们已经探索过了,在向类第一次发送消息的时候,非懒加载类才会开始加载,而根据我们上一章类的加载探索内容,在 realizeClassWithoutSwift方法的最后有一个 methodizeClass方法,在这个方法里面会有一个** Attach categories**的地方:


此时 LGTeacher类里面是没有方法的,这里读取 rw却有一个结果,我们不难看出这是位于 LGTeacher+test分类中的一个 initialize方法,这个方法是我手动加个这个分类的。这样进一步证明了,如果是懒加载类,并且分类也是懒加载,那么分类的加载并不会来到 unattachedCategoriesForClass,而是直接在编译时加载到了类的 ro里面,然后在运行时被拷贝到了类的rw里面。
4.1.2 与非懒加载类配合加载
当类为非懒加载类的时候,走的是 _read_images 里面的流程,这个时候我们的懒加载分类是在哪加载的呢?
我们直接在 methodizeClass 方法中打上断点,并做了一下简单的判断:

4.2 分类实现load方法
4.1.1 与懒加载类配和加载
其实懒加载和非懒加载的最大区别就是加载是否提前,而实现了 +load方法的分类,面对的是懒加载的类,而懒加载的类我们前面已经知道了,是在第一次发送消息的时候才会被加载的,那我们直接在lookupImpOrForward => realizeClassMaybeSwiftAndLeaveLocked=> realizeClassMaybeSwiftMaybeRelock=> realizeClassWithoutSwift=> methodizeClass流程中的 methodizeClass打上断点,看下在这里分类会不会被加载:




4.1.2 与非懒加载类配合加载
非懒加载类的流程我们也十分熟悉了,在 _read_images 里面进行加载,而此时,分类也是非懒加载。我们还是在 methodizeClass 里面进行断点:


static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
其实 attachCategories这个方法只会在实现了 load方法的分类下才会被调用,而来到 attachCategories之前又取决于类是否为懒加载,如果是懒加载,那么就在 load_images里面去处理,如果是非懒加载,那么就在 map_images里面去处理。
总结
分类的加载如果有load方法,那么运行时决定,如果没有实现load方面,那么编译时决定。
- 懒加载分类 + 懒加载类
类的加载在第一次消息发送的时候,而分类的加载则在编译时
- 懒加载分类 + 非懒加载类
类的加载在 _read_images 处,分类的加载还是在编译时
- 非懒加载分类 + 懒加载类
类的加载在 load_images 内部,分类的加载在类加载之后的 methodizeClass
- 非懒加载分类 + 非懒加载类
类的加载在 _read_images 处,分类的加载在类加载之后的 reMethodizeClass