一、isa 是什么?
通过iOS源码alloc init new 分析
可以知道,对象通过alloc创建对象,分配内存,最后还调用了initInstanceIsa来初始化isa属性。Objective-C 对象在底层本质上是结构体,所有的对象都包含一个isa属性,用来关联对象和存储一些对象的信息。
isa 是对象中的第一个属性,因为这一步是在继承的时候发生的,要早于对象的成员变量,属性列表,方法列表以及所遵循的协议列表。
下面我们先看看initInstanceIsa实例方法点进来是什么
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
函数一开始的 assert(!isTaggedPointer()); 是断言对象的指针不是 Tagged Pointer.
接下来就是对 isa 类型的判断 if (!nonpointer) { ... } else { ... },如果 isa 不是 nonpointer,也就是单纯的指针(Class 类型),则 isa 就直接被赋值为 cls。如果 isa 是 nonpointer,也就是 isa_t 类型的话,则先初始化一个所有位为 0 的指针 isa_t newisa(0)。
此时我们点进去看看isa_t是什么
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
};
然后在看看ISA_BITFIELD是什么
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
• nonpointer: 表示是否对 isa 指针开启指针优化
• 0: 纯 isa 指针
• 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等
• has_assoc: 关联对象标志位,0 没有,1 存在
• has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
• shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
• magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间
• weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放。
• deallocating: 标志对象是否正在释放内存
• has_sidetable_rc: 当对象引用技术大于 10 时,则需要借用该变量存储进位
• extra_rc: 当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
由此可以得知 isa_t是一个联合体/位域,联合体的特性就是内部所有的成员共用一块内存地址空间,也就是说isa_t、cls、bits会共用同一块内存地址空间,这块内存地址空间大小取决于最大长度内部成员的大小即64位8字节。由此我们可以知道isa 的所占的内存空间大小为8字节。
位域定义:
指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
关于 SUPPORT_INDEXED_ISA 宏,它的定义是这样的:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
这个ARM_ARCH_7K应该是对watchOS的一种处理,有兴趣的可以查一查。 这里我们直接看define SUPPORT_INDEXED_ISA 0的情况,所以直接看else分支里就可以了
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
可以看到,先将 newisa 的 bits 赋值为常量 ISA_MAGIC_VALUE,里面包括了 magic 和 nonpointer 的值。然后将是否有 C++ 析构函数标示上,最后将位移(shift)后的 cls 存入 shiftcls(类指针按照 8 字节对齐,所以最后三位一定是 0,所以可以右移三位来节省位的使用)。
bits中的其他说明
- has_assoc 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
- weakly_referenced 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
- deallocating 对象正在释放内存
- has_sidetable_rc 当对象引用技术大于 10 时,则需要借用该变量存储进位。
- extra_rc 表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
二、isa走位分析
我们都知道Object-C的对象其本质就是结构体,前面我们也分析了每一个对象都会有一个isa。同时我们也知道了其实类的本质也是一个结构体,而且是继承自objc_object的。所以说每一个类也都有一个isa,接下就通过官方提供的isa走位图来看一下
然后我们通过代码来验证一下isa的走位

网友评论