1.前言
对象在计算机中的存储是无序的,内存中的一段地址空间,有可能表示A类声明的对象,也可能是B类声明的对象。我们根据一个对象,可以找到对象所在的地址空间,在计算机硬盘中,地址空间存储的是0和1。但是计算机是怎么知道0和1所代表的含义?为什么可以根据对象找到自己的属性和方法,还可以找到父类的属性和方法,这就是isa的作用。
我们自定义了一个Person类,没有任何属性和方法,为什么我们可以调用 alloc
和 init
呢 ?
因为Person类继承自NSObject,NSObject里有默认的实现,这个方法是苹果在NSObject中给我们提供的
+ (id)alloc {
return _objc_rootAlloc(self);
}
那继承自NSOject的类是怎么实现调用NSObject的方法?或者说子类是怎么实现调用父类方法的呢?
在OC中所有对象都有isa,而且是结构体中第一个属性。isa的作用就是将地址空间与类信息相对应起来。那isa是怎么实现的呢?那要研究isa具体包含哪些信息。
2.isa的完整内容
isa是怎么生成的呢?通过查看objc_object结构体,看出是isa是由isa_t的定义,从isa_t的实现代码中可以看出是通过联合体(union)
定义的。
isa_t
在781源码中的表示如下:
union isa_t { //联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t
在最新的838源码中:苹果对对象的释放又做了优化:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
小结:通过分析isa_t,它是联合体类型,我们知道了它由两种值:一种是cls,一种是bits。但是它最终的返回值都是Class。
2.1 关于Class
为什么isa的类型是Class?其根本原因是由于isa
对外反馈的是类信息
,isa返回时做了一个
类型强制转换。可以通过如下代码理解:
在最新的838源码中:苹果给isa加上了索引。
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
//ISA方法中会调用如下方法
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
if (nonpointer) {
return classForIndex(indexcls);
}
return (Class)cls;
#else
return getClass(authenticated);
#endif
}
在不支持isa索引化的情况下,通过getClass获取isa
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
# endif
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
小结:苹果最新的方向是用索引代替mask,面具方式即将退出舞台。应该是出于扩展性的考虑,用classForIndex(indexcls)
这种方式,不再局限于位运算。
2.2 关于ISA_BITFIELD
isa_t的联合体中,有ISA_BITFIELD
这个内容,它是以一个宏定义的方式来表示:
在781的代码中是下面👇:shiftcls分别是arm64下33位,x86_64下44位
在最新的838中:deallocating移出到isa_t结构体中。
nonpointer
(存储在第0字节) : 是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa。has_assoc
(存储在第1个字节): 关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存。has_cxx_dtor
(存储在第2个字节): 析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象。shiftcls
:(存储在第3-35字节)存储类的指针,类的地址, 即类信息。其实就是优化之前 isa 指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。arm64-not-e中有52位。magic
(存储在第36-41字节):判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。weakly_referenced
(存储在第42字节):对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。deallocating
(存储在第43字节):标志对象是否正在释放内存。处理逻辑移到了isa_t中。has_sidetable_rc
(存储在第44字节):判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。extra_rc
(存储在第45-63字节。):存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc 的值就为 9。
2.3 isa的初始化代码
我们知道isa
与类进行关联是通过obj->initInstanceIsa
。
在838的源码中:根据是否有析构函数,有两个objc_object::initInstanceIsa方法
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initInstanceIsa(Class cls, bool)
{
initIsa(cls);
}
如果没有析构函数,默认按false执行
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
所以isa的创建和最终各个位的赋值由以下objc_object::initIsa
代码决定:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// 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;
}
2.4关于验证shiftcls位域中存储的类信息
有以下几种验证方式:
- 【方式一】通过
initIsa
方法中的newisa.shiftcls = (uintptr_t)cls >> 3;
验证 - 【方式二】通过
isa指针地址
与ISA_MSAK
的值&
来验证 - 【方式三】通过runtime的方法
object_getClass
验证 - 【方式四】通过
位运算
验证