ObjC-Runtime源码注疏(二)
前言
在上一片注疏(ObjC-Runtime源码注疏(一))中,我们已经完整介绍了isa指针的源码,而从本篇幅开始,就开始完整介绍OC对象相关的源码了。
现在我们就要再次从源码的角度去看看OC中的对象objc_object。
objc_object的原码与isa的原码一样,都定义在了objc-private.h中。考虑到objc_object的原代码很长,我们将在下面分段进行说明。
另外,了解OC的同学都知道,objc_object当中isa指向的类型就是objc_class对象,那为啥我们不先介绍objc_class呢?
那是因为在objc-runtime-new.h中,objec_class实际上是objc_object的子类,所以,我们才要先介绍objc_object。
定义&初始化
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
......
}
从以上定义我们可以看到,objc_object是一个结构体。它拥有一个isa指针。同时,还拥有一大堆与isa相关的方法和一大堆的初始化方法。
// initIsa() should be used to init the isa of new objects only.
从代码的注释中,我们看到initIsa()方法就是在生成一个新对象时调用初始化isa的。那我们就从这个函数开始吧。
我们先来看看initIsa(Class cls)函数的定义和实现吧。
//声明
void initIsa(Class cls /*nonpointer=false*/);
//实现
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
//真正的调用函数
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;
}
}
从上面的代码段可以看到initIsa(Class cls,bool nonpointer,ool hasCxxDtor)就是初始化isa最终的执行函数。
函数的入参分别是Class,即objc_class的指针;第二个参数为bool值,标识是否为taggedpointer;第三个参数为bool值,标识是否含有C++或者OC的析构函数。第二和第三两个如参默认都是false。
进入函数的第一行就是一个断言,通过isTaggedPointer()函数来判断当前isa是不是一个taggedPointer类型的指针。我们继续深挖isTaggedPointer()的实现源码
inline bool
objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
...
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
...
define _OBJC_TAG_MASK (1UL<<63)
通过不断的深挖,我们得知最终负责判断当前isa是不是一个taggedpointer的函数是_objc_isTaggedPointer(const void * _Nullable ptr)。
其内部实现就是通过_OBJC_TAG_MASK掩码来获得其结果。而_OBJC_TAG_MASK掩码是左移63位,也就是说移动到最高位。结合前面isa结构体的介绍,最高位实际上就是nonpointer的标记位。
至此,我们就清楚了。系统就是通过判断isa的nonpointer位来判断当前isa是不是一个taggedpointer类型的指针。
我们继续回到initIsa(Class cls, bool nonpointer, bool hasCxxDtor)函数的源码。
断言之后,直接就是一个针对入参nonpointer的判断,如果不是nonpointer类型,则直接对isa变量赋值,且初始化的类型就是isa指向的Class对象的地址。
如果是nonpointer类型,则仍旧先进入两个断言,这两个断言实际是判断当前版本是否可以支持nonpointer类型的Isa指针。以及是否已经有了实例的原始类型。
在两个断言都未触发的情况下,执行下一系列代码:
- 用0值初始化一个新的isa指针 -> newisa
- 判断是否支持indexed_isa,这部分先往后放一放
- 对bits用ISA_MAGIC_VALUE进行赋值。ISA_MAGIC_VALUE的掩码值为0x001d800000000001ULL,按照64位分解可得00000000-0-0-0-111011-00000000000000000000000000000000000000000000-0-0-1,此处应注意到iOS小端字节序列,高位在右侧。此时我们再对照x86的isa指针内部位的分布图来对照可知,ISA_MAGIC_VALUE最终将nonpointer位赋值为1,即当前是一个nonpointer指针,之后在image字段上赋值,这个值应该与后面的编译器相关,此处就不再展开了。
- 对isa的has_cxx_dtor字段赋值,同时将传进来的cls的shiftcls字段指向isa的shiftcls字段的位置
- 将newisa赋给isa
至此,我们就完成了isa的初始化工作。
下面,我们说说刚才跳过的indexed_isa的支持的宏判断
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
从SUPPORT_INDEXED_ISA宏的定义,就可以看到,其实是否支持indexed_isa与当前设备的CPU架构有直接关系。
从宏的判断,我们可以猜测如下:
- "ARM_ARCH_7K >= 2" 与AppleWatch的CPU型号有关,其版本要>=2
- "arm64" 是判断当前系统是否为arm64位
- "!LP64" 即系统不能是LP64。所谓LP64,指的是int类型为32位,long类型为64位,pointer类型也为64位才被称为LP64
所以说,从以上来看,我们的iphone手机是不支持indexed_isa结构的。
后面我们再看看剩余的几个init方法:
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
void initClassIsa函数
inline void
objc_object::initClassIsa(Class cls)
{
if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) {
initIsa(cls, false/*not nonpointer*/, false);
} else {
initIsa(cls, true/*nonpointer*/, false);
}
}
从该函数实现上看,如果当前runtime不支持nonpointer 或者 根据cls->instancesRequireRawIsa()的返回值为真,则直接调用initIsa方法,只不过nonpointer参数为false;负责还是调用initIsa函数,只不过nonpointer为true。
对于DisableNonpointerIsa变量,我们可以看到如下定义:
//objc-env.h
OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields")
这块的代码就是从当前环境中为DisableNonpointerIsa赋值,即代表当前runtime环境是否支持nonpointerisa。
对于instancesRequireRawIsa()方法,我们能看到如下定义:
#define FAST_CACHE_REQUIRES_RAW_ISA (1<<13)
...
bool instancesRequireRawIsa() {
return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
}
可以看到从cache读取第13位作为返回值。至于cache是什么,我们后面还会提及。
initProtocolIsa函数
inline void
objc_object::initProtocolIsa(Class cls)
{
return initClassIsa(cls);
}
从代码中可以看到,对于protocol协议对象,直接调用了initClassIsa函数,没有其他操作
initInstanceIsa函数
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
从实现中看到,调用initInstanceIsa时,nonpointer直接为true,所以这个函数必然是支持初始化nonpointer指针的。
最后,我们回过头来,去看一看objc_object中最开始定义的几个方法
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
ISA() 函数
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
函数返回值就是一个objc_class的结构体。函数在内部仍旧先判断是否为taggedpointer指针,如果是直接就中断。之后又通过SUPPORT_INDEXED_ISA宏来定义了不同的执行逻辑。之前已经说过SUPPORT_INDEXED_ISA与设备相关,一般的iphone是不支持的。所以只有最后一句被执行。
return (Class)(isa.bits & ISA_MASK);
这句实际上就是直接将isa.bits中的44位(x86)或33位(arm64)保存的地址强转成Class而已。
rawISA()函数
inline Class
objc_object::rawISA()
{
ASSERT(!isTaggedPointer() && !isa.nonpointer);
return (Class)isa.bits;
}
从源码可知,不是taggedpointer并且nonpointer位也不能是1的情况下,直接返回isa.bits;
getIsa()函数
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
首先,代码中又出现了些心的东西,如fastpath和slowpath。这两个是什么的东西?
经过查找,我们看到了如下定义:
//objc-os.h
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
//llvm-DenseMap.h
#define LLVM_UNLIKELY slowpath
#define LLVM_LIKELY fastpath
在代码中找到两组定义,但感觉其实都差不多。对于__builtin_expect()函数,解释如下:
__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1)表示 x 的值为真的可能性更大;
__builtin_expect((x),0)表示 x 的值为假的可能性更大。
说白了就是fashpath(x)代表x很可能为真,slowpath(x) 代表x很可能为假。要求编译器优先处理判断各自分支。这里说的优先判断,可以理解为预先读取下一条指令。
llvm-DenseMap.h中的定义,unlikely和likely实际上与之前的定义也是一样的。
所以代码中的fastpath(!isTaggedPointer()),其含义就是大概率当前判断的对象不是一个taggedpoiner对象。
回到getIsa()函数本身。首先判断了当前isa如果不是taggedpointer,则直接调用ISA()函数返回。
如果是taggedpointer,则首先声明了slot变量,同时,将当前isa赋值给ptr。
其次,通过(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK 计算出一个值,从下面的代码看slot的值是一个下标。
其中,_OBJC_TAG_SLOT_SHIFT的值为60,也就是说ptr右移60,这样就留出了地址的高四位。然后在与上_OBJC_TAG_SLOT_MASK就得出了slot的值。
再次,使用该slot下标在objc_tag_classes数组中获取对应的class指针。
那么,我们再看看objc_tag_classes是什么。
extern "C" {
extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];
extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes
...
#define _OBJC_TAG_SLOT_COUNT 16
#define _OBJC_TAG_EXT_SLOT_COUNT 256
结合代码我们可以这样理解。实际上在系统中已经定义好了所有taggedpointer的类型。对于所有taggedpointer对象的isa中是不存储对应的对象类型的,而是通过isa头4位来计算出对应类型的下标,然后从系统定义的taggedpointer类型中获取对应的Class类型。这样做的好处就是扩充了taggedpointer对象isa内存储值的空间。
从上面的定义来看,系统定义了16种类型。而对于用户自己扩展的类型,给了一个256大小的空间用来存储用户自定义出来的taggedpointer对象类型。
好了,再次回到getIsa的代码中,紧接着通过slowpath函数优化了判断语句,即大概率不会是系统不认识的taggedpointer类型。如果真的是不认识的,就说明是用户自定义扩展的,那么直接走前面说过的用户自定义数组objc_tag_ext_classes来获取。
最终,返回对应的Class,getIsa()方法结束
至此,我们把objc_object结构体中的前半段函数都介绍了一遍,我们已经高清楚objc_object在声明后是如何初始化自己的isa的。下一章节,我们再看看objc_object里的其他函数。