在 OC -- 类的加载(上)文章中,我们介绍了非懒加载类的初始化过程,今天我们来看下懒加载类是什么时候加载的。
懒加载类的加载
我们知道,即便是懒加载类,它在加载的时候也会有对rw或者ro的赋值等操作,会走到realizeClassWithoutSwift方法里,在这个方法里我们加个断点,如下图,看看自定义的懒加载类XXTeacher在加载的时候会不会走到这里。
运行项目,发现并没有走到这个断点。
然后,我们在main函数中,添加一句使用到XXTeacher类的代码后再运行,发现代码走到了上面添加的那个断点了:
此时,我们看下栈信息:
在执行了main方法以后,要去走objc_alloc方法,但是没在缓存里面找到这方法(_objc_msgSend_uncached),然后就去方法列表里去找(lookUpImpOrForward),然后再经过一系列逻辑,走到了realizeClassWithoutSwift方法里。这就是在执行[XXTeacher alloc]时,底层的逻辑。
所以,我们可知,懒加载类是在其第一次接收到消息的时候去加载的。
分类的加载
在main.m文件里,添加了XXMan类,以及名为“XX”的分类。
使用clang命令编译一下main.m文件,打开编译得到的main.cpp文件,看到最底下有个_category_t的结构体,这就是XXMan的XX分类对应的结构体。
在这个cpp文件中搜索struct _category_t:
看到, 这个分类的结构体中有一个class_methods,说明分类是没有元类的。 它没有ivars,说明它没有对成员变量进行存储,所以从这里我们也能看出为什么不能给分类添加成员变量。
关联对象
在cpp文件中也能看到分类中添加的方法,但是却找不到属性对应的setter和getter方法。
我们知道给分类添加属性就需要使用到关联对象:
通过以上两个方法,我们就能实现关联对象的赋值和取值,那么这两个方法具体做了什么呢?我们来看下。
点击进入objc_setAssociatedObject方法,看到它调用了_object_set_associative_reference方法,其中有这样三行代码:
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
association.acquireValue();
其中,DisguisedPtr是对对象的一个封装,ObjcAssociation是对关联策略和值的一个封装。
acquireValue()方法是针对关联策略的一个处理。
接下来:
图1
其中AssociationsManager的作用是给这段代码加锁,保证安全。
AssociationsHashMap associations是全局唯一的一张哈希表:
我们看到,AssociationsHashMap里面存的就是键值是DisguisedPtr和ObjectAssociationMap,而在ObjectAssociationMap中存的就是上面说到的ObjcAssociation。
associations调用了try_emplace方法,点击进入这个方法:
这个方法是通过key去哈希表中遍历,如果没找到,会把这个键值对插入到这个哈希表中,不管找没找到,最后都会返回一个迭代器。看上面的图1,得到的这个迭代器,接下来会去更新这个哈希表。
如果value值为空的话,就会删除这个关联。
以上就是关联对象的set的流程。我们再来看下get的流程。
这个find方法的参数object,就是我们在上面的set方法中的DisguisedPtr,这是在全局的哈希表中找到了当前类对应的一个迭代器。继而又找到了ObjectAssociationMap,再根据key找到对应的ObjcAssociation,就能拿到value了。
类和分类的组合加载
我们看main函数中:
分类中的属性name为什么可以直接使用呢?这个时候分类也已经加载过了吗?下面我们来看下分类的加载时机。
我们知道类有懒加载类和非懒加载类之分,分类也是如此,所以根据是否是懒加载,类和分类的组合就有四种情况。其中,括号内表示是否为懒加载:
1、类(非)+ 分类(非)
attachCategories是连接分类的方法,当类和分类都是非懒加载的时候,会走到这个方法,在这个方法里加个断点,调试一下:
ro里面只有两个方法,正好是类里面的方法,分类的方法没有加载到ro里。
attachCategories方法,会创建class_rw_ext_t结构体rwe,然后走到attachLists方法,生成一个数组,把类中的方法放在数组的后边,把分类中的方法放到数组的前面,这个数组会被赋值到rwe->methods。从上面我们也可得知,当类和分类中有同名方法的时候,走的会是分类的方法,因为它在方法查找的时候会先被找到。
2、类(非)+ 分类(是)
运行后也会加载对应的类,会断在realizeClassWithoutSwift方法里,我们来看下ro信息:
ro里面包含了类和分类里面的方法。它不会再走到attachCategories方法里,也不会再创建class_rw_ext_t结构体。
3、类(是)+ 分类(非)
同2中情况一致!
4、类(是)+ 分类(是)
如果我们未使用到该类,它不会加载。我们添加了一句使用到该类代码,重新运行,效果同2和3,
总结
这篇文章我们介绍了懒加载类和分类的加载时机。至此,我们了解了所有的类和分类的加载过程和时机:非懒加载的类和分类在项目启动的时候就去加载了;如果类和分类其中一个是非懒加载的,也会在项目运行的时候就都加载了;只有当类和分类都是懒加载的时候,才会在第一次被使用到的时候去加载。