isa_t类型详解
在新版的runtime源码中,NSObject类型最终会转化为object_class类型,而object_class集成自objc_object,在结构体objc_object中就含有isa_t类型的成员isa
查看isa_t的源码,其中处理两个构造函数外,有一个cls指针,还有一个uintptr_t类型的成员bits以及一个结构体:
union isa_t {
isa_t(){ }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
}
查看结构体的源码可以发现,在结构体中使用位域来存储了很多信息,此处展示arm64架构下的源码信息
#define ISA_MASK 0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK 0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
};
isa_t位域存放信息类型
isa_t作为公用体,内部使用8个字节的内存空间,共64位二进制,存放了以下信息
* nonpointer代表是否是优化过的isa指针,占用1位。
* 1:表示新版本的isa指针,使用位域来存储信息
* 0:旧版本普通的isa指针,直接存储Class和Meta-Class内存地址
* has_assoc代表是否有关联对象,占用1位,一旦设置过关联对象,则会置为1。如果添加过关联对象,在释放时会检测是否有关联对象,所以释放会更慢。
* has_cxx_dtor代表是否实现了C++的析构函数(.cxx_destruct),如果没有,释放时的速度会更快。占用1位
* shiftcls中存放着类或者元类的内存地址,占用33位。
* magic是调试时用来判断对象是否完成初始化,占用6位
* weakly_referenced代表是否被弱引用指向过,占用1位,如果为0,则释放时速度会更快
* deallocating用来表示对象是否正在释放,占用1位
* extra_rc用来存储引用计数的值,占用19位,此处需要注意的时,它存储的是引用计数的值-1。如果对象的引用计数为1,则extra_rc中存储的值为0
* has_sidetable_rc用来表示是否将引用计数存储在SideTable中,引用计数的值过大,在extra_rc无法存储,则会将引用计数存放到SideTable当中。
Class底层结构分析
* 上面我们已经分析来isa_t的结构
struct objc_object {
private:
isa_t isa;
public:
......
}
* objc_class继承自结构体objc_object,结构可以简化如下
struct objc_class{
Class ISA; //isa指针,通过位域存放多个信息
Class superclass; //supperClass
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用来获取类的具体信息
}
ojbc_class 除了有isa指针外,还保存了父类的class,方法缓存以及当前类的一些基本信息。
查找class_data_bits_t
- 其实一开始是没有class_rw_t的,而存放的是class_ro_t,class_rw_t是在之后进行创建的。
- class_rw_t是可读可写的,它包含了类的初始内容,分类的内容。
- class_ro_t是只读的,它包含了类初始化的内容,并且在编译完成后就决定了,在运行时无法进行修改。
源码分析
上文提到,在类初始化的时候其实class中保存的是class_ro_t而不是class_rw_t,这一点可以通过objc-runtime-new.mm中的realizeClassWithoutSwift函数可以看出
static Class realizeClassWithoutSwift(Class cls){
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
//如果class已经初始化,则直接返回当前class
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
//首先通过class的data()函数取到class中bits中存放的class_ro_t
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
//如果当前的cls是future class,并且rw已经被创建,则直接拿到rw和rw中的ro
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
//如果是普通的class,创建rw
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
//将ro赋值给rw中的ro
rw->ro = ro;
//设置rw的flags
rw->flags = RW_REALIZED|RW_REALIZING;
//将rw设置到cls中的bits中去
cls->setData(rw);
}
......
//递归初始化父类
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
//递归初始化元类,通过isa指针来获取到cls的元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
......
//修改rw中的方法列表,属性列表和协议列表,并且将分类中的方法列表,属性列表和协议列表附加到rw中去
methodizeClass(cls);
}
在类初始化时,cls通过data()函数获取到的其实是class_to_t,内部存放了类初始的方法列表、属性列表和协议列表。如果当前cls是普通的class,则通过calloc函数创建rw,然后将rw中的ro指针指向原始的ro(class_ro_t),之后重置rw中的flags,并将rw的内存地址保存到cls的bits中去。并且,函数中首先是通过递归初始化当前父类以及元类。最后才初始化当前类。
创建完rw(class_rw_t)后,则会重新整理cls中的方法列表、属性列表和协议列表。具体查看methodizeClass函数源码:
static void methodizeClass(Class cls){
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// 从ro中拿到baseMethodList
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
//将baseMethodList附加到rw的methods中去
rw->methods.attachLists(&list, 1);
}
// 从ro中拿到baseProperties
property_list_t *proplist = ro->baseProperties;
if (proplist) {
//将baseProperties附加到rw的properties中去
rw->properties.attachLists(&proplist, 1);
}
//从ro中拿到baseProtocols
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
//将baseProtocols附加到rw的protocols中去
rw->protocols.attachLists(&protolist, 1);
}
//最后将所有Category的方法列表、属性列表和协议列表附加到cls
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
methodizeClass 函数中首先会拿到ro中的方法列表、属性列表和协议列表,然后将拿到的方法、属性列表和协议列表通过对应的attachLists函数附加到rw的二维数组中去。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
//这里以方法列表为例
//array()->lists表示原来类中的方法列表
//addedLists表示所有Category中的方法列表
if (hasArray()) {
//获取原来类中方法列表的长度
uint32_t oldCount = array()->count;
//得到方法合并之后的新的数组长度
uint32_t newCount = oldCount + addedCount;
//给array重新分配长度为newCount的内存空间
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//将原来array()->lists中的数据移动到数组中oldCount的位置
//也就是相当于将array()->lists的数据在内存中往后移动了addedCount个位置
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//将Category中的方法列表copy到array()->lists中
//并且是从数组的起始地址开始存放
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
安装完类本身的方法、属性和协议后,会继续通过attachCategories函数拿到class的所有Category中的方法、属性和协议列表,然后调用attachLists函数附加到rw中的二维数组中去。
//将方法列表、属性列表、协议列表附加到类中去
//假设cats中的所有的类别都是按顺序进行加载和排序的,最早装载进内存的类别是第一个
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
//用来判断是否是元类
bool isMeta = cls->isMetaClass();
//申请连续内存空间,创建一个二维数组,里面存放着所有的method_list_t
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//申请连续内存空间,创建一个二维数组,里面存放着所有的property_list_t
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//申请连续内存空间,创建一个二维数组,里面存放着所有的protocol_list_t
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
//获取到category_list之后,通过逆序遍历来取出Category内部的方法、属性和协议列表
while (i--) {
auto& entry = cats->list[i];
//遍历cls所有的category_t,将category_t中的method_list_t取出,存放到二维数组mlists中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 将category_t中的property_list_t取出,存放到二维数组proplists中
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//将category_t中的protocol_list_t取出,存放到二维数组protolists中
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//拿到类对象cls的class_rw_t类型的成员data,它是可读可写的
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//将方法列表合并到rw的方法列表中去,并且插入到表头位置
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//将属性列表合并到rw的属性列表中去,并且插入到表头位置
rw->properties.attachLists(proplists, propcount);
free(proplists);
//将协议列表合并到rw的协议列表中去,并且插入到表头位置
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
因为是先附加类本身的方法、属性和协议,之后附加Category的方法、属性和协议,并且attachLists操作从数组的头部开始进行附加,所以先执行附加操作的方法、属性和协议会放到数组的后面,因此类本身实现的方法、属性和协议肯定放在rw二维数组的最后一个元素。
说一下对isa指针的理解
isa等价于 is kind of
- 实例对象isa指向类对象
- 类对象isa指向元类对象
- 元类对象isa指向元类的基类
isa有两种类型
* 纯指针,指向内存地址 Class 和 metaClass的内存地址
* NON_POINTER_ISA,除了内存地址外,还存放了一些其他的信息