isa
对于大家来说应该并不陌生,不管是各个公司的面试题或者说是平时的开发当中都会经常被提及,另外在我们之前对alloc
的源码分析时也发现,最后一步obj->initInstanceIsa(cls, hasCxxDtor)
便是对isa
的初始化.今天我们就跟随源码一起来看一看isa
到底是个怎样的存在
联合体位域
在讲isa
之前,我们先来学习一个概念:联合体位域.
联合体与结构体
结构体(struct)中所有变量是"共存"的--优点是"有容乃大",全面;缺点是内存空间的分配是粗放的,不管用不用,全分配
联合体(union)中各变量是"互斥"的--缺点是不够"包容";优点是内存使用更为精细灵活,节省了内存空间
举例说明:
存在如下四个属性
@property (nonatomic, assign) int front; // 1:正在向前 2:没有向前
@property (nonatomic, assign) int back; // 1:正在向后 2:没有向后
@property (nonatomic, assign) int left; // 1:正在向左 2:没有向左
@property (nonatomic, assign) int right; // 1:正在向右 2:没有向右
如上,每个int占用四个字节,总共占用4*4=16
个字节,共16*8=128
位,造成了大部分内存浪费,
而用联合体位域表现如下:
// 联合体
union {
char bits; // 占用1位,8个字节,二进制表示为0b00000000
// 位域
struct { // 0000 1111
char front : 1; // 占用bits的第一个字节
char back : 1; // 占用bits的第二个字节
char left : 1; // 占用bits的第三个字节
char right : 1; // 占用bits的第四个字节
};
} _direction;
#define LYDirectionFrontMask (1 << 0)
#define LYDirectionBackMask (1 << 1)
#define LYDirectionLeftMask (1 << 2)
#define LYDirectionRightMask (1 << 3)n
// 初始化
_direction.bits = 0b0000000000;
// 赋值
_direction.bits |= LYDirectionFrontMask;
// 取值
return _direction.front;
isa源码定义
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定义如下:
// arm64架构下
# define ISA_BITFIELD \
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
// x86_64架构
# 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
通过源码我们可以发现isa
采用了联合体的结构.arm64
架构与x86_64
架构主要区别在shiftcls
和extra_rc
占用的长度.
isa存储内容
接下来我们一起看下isa
中定义的各个字段具体指什么(arm64
架构下)
nonpointer
(存储在第0字节)是否为优化isa
标志。0代表是优化前的isa
,一个纯指向类或元类的指针;1表示优化后的isa
,不止是一个指针,isa
中包含类信息、对象的引用计数等。现在基本上都是优化后的isa
。has_assoc
(存储在第1个字节)关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存(dealloc
的底层代码有体现)。has_cxx_dtor
(存储在第2个字节)析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象(dealloc
的底层代码有体现)。shiftcls
(存储在第3-35字节)存储类的指针,其实就是优化之前isa
指向的内容。在arm64
架构中有33位用来存储类指针。x86_64
架构有44位。magic
(存储在第36-41字节)判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。weakly_referenced
(存储在第42字节)对象被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放(dealloc
的底层代码有体现)。deallocating
(存储在第43字节)标志对象是否正在释放内存。has_sidetable_rc
(存储在第44字节)判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。extra_rc
(存储在第45-63字节。)存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc
的值就为 9
由上我们可以知道shiftcls
中存储了类的相关信息,接下来我们通过源码来验证下
// isa初始化
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)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;
}
}
通过代码我们可以发现所谓的isa
和对象进行关联就是将cls
的信息存储在新的isa
的shiftcls
上,因为类信息的存储是从第四位开始的,所以需要将cls
右移3位,即:newisa.shiftcls = (uintptr_t)cls >> 3;
我们再来看下newisa
赋值前后的值分别是什么
// 赋值前,即执行newisa.shiftcls = (uintptr_t)cls >> 3;前
(lldb) p newisa
(isa_t) $4 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
// 赋值后
(lldb) p newisa
(isa_t) $5 = {
cls = LYPerson
bits = 8303516107940081
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 536871966
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
通过赋值前后的对比我们发现新的isa
的cls
变为了LYPerson
,shiftcls
也由0变成了536871966.由此也证明了isa
与对象关联就是将类信息存入到isa的shiftcls
中
除此之外,我们还可以通过移位来证明,因为arm64
下shiftcls
占用3~35共33位,所以我们可以通过先右移3位,再左移30位,最后再右移27位来获取shiftcls
的值,如下:
(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d8001000024dd >> 3
1037939513492635
(lldb) po 1037939513492635 << 30
562951189692416
(lldb) po 562951189692416 >> 27
Person
(lldb)
当然,对于左移右移我们还可以通过位运算&
来实现,如下
# define ISA_MASK 0x0000000ffffffff8ULL
(lldb) po 0x001d8001000024dd & 0x0000000ffffffff8ULL
Person
至此,我们对isa
的底层源码以及存储的值信息已经有了一定的了解,以后遇到关于isa
的相关问题也能回答的更自信一些了.