类的加载原理:
iOS 类的加载原理上
iOS 类的加载原理中
iOS 类的加载原理下
分类的加载原理补充及类扩展 , 关联对象介绍
在 iOS 类的加载原理上 中我们讲到 readClass
方法,该方法通过类的地址对类的名称与地址进行绑定匹配。但是我们还不了解类的具体实现过程,这里我们来继续探究一下。
realizeClass 分析
我们在
_read_images
函数中找到跟类处理相关的代码,并在这两处地方加上测试代码并打上断点,运行之后发现代码只执行了 3781
行的打印(这里执行的前提是实例的 load
方法要有调用),紧接着执行了 realizeClassWithoutSwift
函数。那么我们就来分析一下 realizeClassWithoutSwift
函数做了哪些事情。
realizeClassWithoutSwift
首先我们判断
cls
为 LGPerson
的时候在 auto ro = (const class_ro_t *)cls->data()
这一行打上断点,来打印 baseMethodList
的信息。正常的情况下这里应该会打印出 LGPerson
类的方法列表,但是这里并没有打印出来。说明在这里方法还没有被加载进来。所以我们继续往下看方法的加载是什么时候实现的。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
// 判断是否是元类
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 不是元类的话会走到这里,这里主要是把 ro 的数据复制到 rw
// Normal class. Allocate writeable class data.
rw = objc::zalloc();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// 如果是元类的话就设置为 non pointer ISA
cls->setInstancesRequireRawIsa();
} else {
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
// 如果做了环境变量相关的配置,这里不管是不是元类,instancesRequireRawIsa 都会赋值为 true
if (DisableNonpointerIsa) {
instancesRequireRawIsa = true;
}
// SUPPORT_NONPOINTER_ISA
#endif
/**
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
上面这两行加上这里两行就构成了类的继承链关系及 isa 走位图
*/
// 这里会设置父类及元类
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
methodizeClass(cls, previously);
return cls;
}
在 auto ro = (const class_ro_t *)cls->data()
之后主要做了以上注释中的这些事情,那么当执行到 methodizeClass(cls, previously)
这行的时候方法是否被加载进来了呢?我们在 methodizeClass
方法打断点看一下。
这里可以看到
baseMethodList
依然没有数据,说明在这里方法依然没被加载进来。那么我们继续来分析在 methodizeClass
方法中做了哪些事情。
methodizeClass 分析
我们继续往下执行,在执行到 prepareMethodLists
的时候可以看到 list
在这里是有值的,只是打印不出来。
prepareMethodLists
紧接着我们来到 prepareMethodLists
方法,在这里会执行到 fixupMethodList
方法,而且打印的 list
地址跟 method_list_t *list = ro->baseMethods();
这里的地址是一样的。接着进入到修复方法。
fixupMethodList
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
// sel_cname 方法会获取到 SEL
const char *name = sel_cname(meth.name());
printf("排序前 : %s - %p",name,meth.name());
// 这里主要是把 sel 中的名字跟地址设置到 meth,这里之后就可以打印成功了
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// 这里会判断是否是 isSmallList,如果时候 isSmallList 就不用重新进行排序,不是 isSmallList 的话就会根据地址对方法列表进行排序
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
for (auto& meth : *mlist) {
// sel_cname 方法会获取到 SEL
const char *name = sel_cname(meth.name());
printf("排序后 : %s - %p",name,meth.name());
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
在 fixupMethodList
方法中会遍历 mlist
,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist
进行重新排序。
最后我们通过打印验证可以看到,方法列表确实重新排序了。
在执行完
fixupMethodList
方法后我们再次打印 baseMethodList
还是没有数据,所以我们接着往下探究。
懒加载类与非懒加载类
非懒加载类
非懒加载类:当前类实现了
load
方法,map_images
的时候加载。
上面我们也讲过,执行 realizeClassWithoutSwift
方法的前提是当前类要实现 load
方法,这时候就是非懒加载类。然后就会执行 read_iamges
-> read_class(名字 - 类)
-> realizeClassWithoutSwift(ro - rw superclass isa)
-> methodizeClass()
-> prepareMethodLists(写入方法名 + 方法列表排序)
这个流程。但是这样的缺点就是比较耗时跟消耗内存,那么如果懒加载会执行哪些流程呢?这里我们注释 load
方法来看一下。
懒加载类
懒加载类:当前类没有实现
load
方法,当前类接收的第一条消息的时候才会加载,节约内存,提高加载速度。
当我们注释
load
方法运行的时候会来到这里执行 realizeClassWithoutSwift
方法。我们在这里加上测试代码,并打上断点 bt
输出函数调用堆栈信息。
这里可以看出懒加载类的加载流程 lookUpImpOrForward
-> realizeClassMaybeSwiftMaybeRelock
-> realizeClassWithoutSwift
-> methodizeClass
。
而且在 main
函数这里可以看到懒加载类的加载执行是在 LGPerson
第一次调用方法的时候。
分类的本质
#import
#import "LGPerson.h"
#import
extern void _objc_autoreleasePoolPrint(void);
@interface LGPerson (LG)
@property (nonatomic, copy, nullable) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod3;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1 {
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2 {
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod3 {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
[p say1];
NSLog(@"Hello, World!");
}
return 0;
}
__attribute__((constructor)) void kcFunc(void){
printf("来了 : %s \n",__func__);
}
我们在 main.m
为 LGPerson
类添加一个分类 LGPerson (LG)
,cd
到 main.m
所在文件目录,然后输入 clang -rewrite-objc main.m -o main.cpp
命令,最后会看到生成了一个 main.cpp
文件。
我们打开
main.cpp
文件,在最后可以看到 LGPerson (LG)
的底层代码。接着我们点进到 _category_t
查看分类的结构。我们可以看到结构体里面有 name
,这里 name
的名称就是 LG
,cls
这里就是就是 LGPerson
类,而且可以看到 instance_methods
跟 class_methods
,这里相对于类的结构多了 class_methods
,这是因为分类没有元类,接着就是 protocols(协议)
跟 properties(关联属性)
。
这里我们也可以看到对应我们添加的类方法跟对象方法,但是这里没有 set
方法跟 get
方法,也验证了分类添加属性是通过关联对象进行处理的。这里是生成的 cpp
文件我们看到的,那么在源码中分类的结构是否跟这里一致呢?
这里可以看到结构跟我们在
cpp
文件中看到的基本一直,只是多了一个 _classProperties
,但是类属性不是一直都有。