一、类的加载
上一篇我们有分析非懒加载类的加载过程,接下来我们可以在_read_images
方法中打印一下,以验证是否加载了我们自己创建的类。
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
printf("_getObjc2NonlazyClassList Class:%s\n",cls->mangledName());
if (!cls) continue;
通过打印结果你会发现,所有实现了+load
方法的类都被正常打印,没有实现这个方法的则没有打印。
懒加载类和非懒加载类有什么区别?
看上面代码注释:Realize non-lazy classes (for +load methods and static instances)
。
1.1 懒加载类的加载
那么懒加载类是什么时候加载的?我们可以调用调用一下Dog类
的alloc
方法
然后进入跟入到lookUpImpOrForward方法(这是一个很重要的方法),alloc
是一个类方法,那此时cls
就应该是Dog的元类
,inst
就是真正的对象。
但是我们通过LLDB
发现,这个inst
还只是一个地址,说明Dog
还没有被初始化。
打断点然后跟进去,最终我们会发现一个我们上一篇分析过的方法
realizeClassWithoutSwift
,这里会进行ro/rw
、category
的操作。这时候打印
inst
(即Dog对象),可以看到rw
有值了,说明类已经被加载。
1.2 总结
下面总结一下懒加载类加载的流程:
- 类第一次加载的时候没有缓存,所以会来到
_class_lookupMethodAndLoadCache3
->lookUpImpOrForward
-
lookUpImpOrForward
会做一次[图片上传中...(懒加载类.png-c5360e-1593754564326-0)]
判断,如果没有实现(!cls->isRealized())
,会来到realizeClassWithoutSwift
,也就是最终实现类加载的地方。
懒加载类
和非懒加载类
的调用堆栈如下:
非懒加载类.png
二、分类的加载
2.1 分类的底层实现
为了探究分类的底层实现,我们先新建一个分类:
@interface Dog (test)
@property (nonatomic,strong) NSString *name;
- (void)sayHello;
+ (void)sayByebye;
@end
用clang命令重写一下:
clang -rewrite-objc Dog+test.m -o category.cpp
在文件末尾,我们可以看到底层方法刚好对应了分类代码:
static struct _category_t _OBJC_$_CATEGORY_Dog_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Dog",
0, // &OBJC_CLASS_$_Dog,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Dog_$_test,
};
static void OBJC_CATEGORY_SETUP_$_Dog_$_test(void ) {
_OBJC_$_CATEGORY_Dog_$_test.cls = &OBJC_CLASS_$_Dog;
}
为了看到分类相对完整的底层实现,建议给新建的分类添加实例方法、类方法、属性。
_OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test
:实例方法
_OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test
:类方法
_OBJC_$_PROP_LIST_Dog_$_test
:属性
同时我们还看到如下代码:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Dog_$_test,
};
这表明分类存储在__DATA
段的__objc_catlist
section里面。
2.2 分类的定义
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_$_Dog
,也就是我们的类对象的定义,所以这里其实就是我们要扩展的类对象,只是在编译期这个值并不存在
instanceMethods
: 分类上存储的实例方法
classMethods
:分类上存储的类方法
protocols
:分类所实现的协议
instanceProperties
:分类所定义的实例属性,不过我们一般在分类中添加属性都是通过关联对象来实现的
_classProperties
:分类所定义的类属性。这里有一行注释:Fields below this point are not always present on disk. 下面的内容并不是一直在磁盘上保存,也就是说他是一个私有属性,但并不是一直都存在的。
三、分类的加载
前面我们已经知道了懒加载类和非懒加载类,分类也分为懒加载和非懒加载。
3.1 懒加载类 & 懒加载分类
从前面的分析,我们知道分类的加载过程:realizeClassWithoutSwift
-> methodizeClass
-> methodizeClass
-> attachCategories
。
通过断点,我们发现
cats
为NULL
,即unattachedCategoriesForClass
并没有获取到分类。但是我们打印ro
和rw
,发现我们methods
是有内容的(sayByebye
是我们手动添加到分类的类方法)。也就是说,如果是懒加载类,并且分类也是懒加载,那么分类在
编译时
直接加载到了类的 ro
里面,然后在运行时
被拷贝到了类的 rw
里面。
3.2 总结
分类的加载可以简单以是否实现load方法
分类:
-
懒加载
:编译时确定 -
非懒加载
:运行时确定
这也说明分类
和类
的加载是不一样的,我们可以把他们分为四类:
-
懒加载分类 & 懒加载类
:
类的加载在第一次消息发送的时候,而分类的加载则在编译时
消息发送的时候 -> lookuporforward -> realizeClassWithoutSwift -> methodlizeClass
不进addUnattachedCategoryForClass,直接走data()
-
懒加载分类 & 非懒加载类
:
类的加载在 _read_images 处,分类的加载还是在编译时
read_images -> realizeClassWithoutSwift -> methodlizeClass -> 不需要添加表 -> 直接在相应data() -> ro
-
非懒加载分类 & 懒加载类
:
类的加载在 load_images 内部,分类的加载在类加载之后的 methodizeClass
3.1 发送消息的时候就去读取 -> realizeClassWithoutSwift -> methodlizeClass
3.2 就是我的类要在消息发送的时候才有 - 但是我的分类提前了 - 需要加载 - read_images - addUnattachedCategoryForClass - 但是没有实现类 就会在下面 prepare_load_methods 实现
3.3 prepare_load_methods - realizeClassWithoutSwift 给你提前了实现类的信息 - unattachedCategoriesForClass
-
非懒加载分类 & 非懒加载类
:
类的加载在 _read_images 处,分类的加载在类加载之后的 reMethodizeClass
read_images -> realizeClassWithoutSwift -> methodlizeClass -> addUnattachedCategoryForClass -> 判断是否实现 -> 这里看到上面一行就在read_images 实现了
if (cls->isRealized()) {
remethodizeClass(cls); -> 实现类信息
}
attachCategories 加载分类数据进来
网友评论