上个篇章中我们探究了类的加载,本篇章我们将对分类进行探究
🌹WYPerson类
@implementation WYPerson
- (void)eat
{
NSLog(@"%s",__func__);
}
+ (void)walk
{
NSLog(@"%s",__func__);
}
@end
🌹WYPerson+test分类
@implementation WYPerson (test)
- (void)instanceMethod
{
NSLog(@"%s",__func__);
}
+ (void)classMethod
{
NSLog(@"%s",__func__);
}
一:揭开分类的面纱
在终端输入如下命令行,将分类转化为底层的cpp
文件
clang -rewrite-objc WYPerson+test.m -o category.cpp
我们打开cpp
文件来到末尾
static struct _category_t _OBJC_$_CATEGORY_WYPerson_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"WYPerson",
0, // &OBJC_CLASS_$_WYPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_WYPerson_$_test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_WYPerson_$_test,
0,
0,
};
我们发现,
WYPerson+test
分类在底层变为一个名为_OBJC_$_CATEGORY_WYPerson_$_test
的结构体,类型是_category_t
下面我们看下_category_t
又是什么?
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
- name: 谁的分类
- cls: 所属的类
- instance_methods: 实例方法
- class_methods:类方法
- protocols:协议
- properties:属性
为了进一步查看_category_t
到底是什么,我们用objc
的源码看一下
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
对比之后发现,两者基本差不多
二:分类的加载过程
在上一篇章对read_image
进行分析的时候,其中也有对分类的处理,系统的分类我们不关注,只关注我们自己的分类是如何加载的,我们添加一段代码进行打印我们自己写的分类。
for (EACH_HEADER) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
auto processCatlist = [&](category_t * const *catlist) {
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
// 🌹🌹添加代码当是我们自己写的分类,打印
const class_ro_t *ro = (const class_ro_t *)cls->data();
const char *cname = ro->name;
const char *oname = "WYPerson";
if (cname && (strcmp(cname, oname) == 0)) {
printf("类名: %s -- %p\n",cname,cls );
}
......这里省略部分代码
}
当代码走完的时候都没有打印,意味着我们的分类压根没有加载,但是通过前面我们对类的探究,知道类的加载分为非懒加载和懒加载,那么这里没有打印是不是因为这个分类是懒加载呢?我们知道实现 +load 方法会提前加载,那么我们为分类添加+load方法。
如图验证了我们的猜想,分类也有非懒加载和懒加载之分。
类别和类常常是共同存在的,没有类的存在,分类也没有存在的必要。现在我们知道不管是类还是分类,都存在非懒加载和懒加载之分,那么他们相结合之后会是如何加载的呢?下面我们将一探究竟。
1. 分类未实现 +load方法
1.1 懒加载分类 + 懒加载类
这种情况下:
分类:未实现 +load方法
类:未实现+load方法
给分类添加+ (void)classMethod
方法并在main.m
中调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
[WYPerson classMethod];
NSLog(@"Hello, World!");
}
return 0;
}
我们知道无论是懒加载类还是非懒加载类都会在realizeClassWithoutSwift
(objc779.1)中进行加载,我们在此方法中加上打印的代码,并在if中执行部分打上断点,查看调用堆栈。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// 🌹打印的代码
if (strcmp(cls->nameForLogging(), "WYPerson") == 0) {
_objc_inform("realizing class: %s", cls->nameForLogging());
}
}
此时我们打印类的ro
信息
......省略部分打印
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 1
first = {
name = "classMethod"
types = 0x0000000100000fa9 "v16@0:8"
imp = 0x0000000100000ea0 (KCObjcTest`+[WYPerson(test) classMethod] at WYPerson+test.m:16)
}
}
}
可以看到我们从类的
ro
信息中就发现了我们分类的方法,而ro
是在编译器就已经确定的,所以当类和分类都是懒加载的时候,分类的方法在编译器就加载到类中。
1.2 懒加载分类 + 非懒加载类
这种情况下:
分类:未实现 +load方法
类:实现+load方法
直接上图
懒加载分类中的方法直接被编译在了ro.baseMethodList中。2. 分类实现 +load方法
2.1 非懒加载分类 + 懒加载类
这种情况下:
分类:实现 +load方法
类:未实现+load方法
我们知道无论非懒加载还是懒加载类最终都会调用realizeClassWithoutSwift
进行类的加载,而在其中的methodizeClass
也有对分类的相关处理,我们在_objc_inform
打上断点
static void methodizeClass(Class cls, Class previously)
{
......省略
// 🌹打印我们自己的类
if (strcmp(cls->nameForLogging(), "WYPerson") == 0) {
_objc_inform("realizing class: %s", cls->nameForLogging());
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
......省略
}
此时我们查看ro
中并没有我们的方法
接着我们查看rw
,发现其中已经有了我们的方法
此时我们看下调用堆栈,其实多了一个prepare_load_methods
看下prepare_load_methods
定义
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++) {
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);
}
}
观察发现,其中也调用了
realizeClassWithoutSwift
来对类进行加载,这里非懒加载分类让我们的懒加载类提前加载了,所以说懒加载类并不一定只会在第一次消息发送的时候加载,还要取决于有没有非懒加载的分类,如果有非懒加载的分类,那么就走的是load_images
里面的prepare_load_methods
的realizeClassWithoutSwift
。
2.2 非懒加载分类 + 非懒加载类
这种情况下:
分类:实现 +load方法
类:实现+load方法
当类为非懒加载类的时候,走的是_read_images
里面的流程,这个时候我们的懒加载分类是在哪加载的呢?
我们methodizeClass
中打上断点
最终会在
methodizeClass
中的attachToClass
方法中调用attachCategories
对分类进行处理
三:总结
懒加载类 + 懒加载分类
- 类的加载在第一次消息发送的时候,而分类的加载则在编译时
懒加载类 + 非懒加载分类
- 类的加载在_read_images处,分类的加载则在编译时
非懒加载类 + 非懒加载分类
- 类的加载在_read_images处,分类的加载在类加载之后的reMethodizeClass
懒加载类 + 非懒加载分类
- 类的加载在load_images处,分类的加载在类加载之后的methodizeClass