本文根据可编译的objc4-750版别源码进行讨论,文章中旦有错误之处,期望大家不吝指正。

Tagged Pointer

在计算机科学范畴,一个指针或地址空间,除了寄存目标地址之外,也能够寄存其他额定的信息,并将其间的一些bit位作为tag符号区别,这个就叫tagged pointer。

详细到Objective-C,一个指针(地址)需求用64位也便是16-byte的内存空间来存储,但实际上往往用不到悉数,只用到了中心的部分,高位16-bit用不到,因为内存按字节对齐的要求,所以低位值也总是为0:

Objective-C runtime源码小记-Tagged Pointer

正常情况下,内存中目标和指针的关系是:一个指针的值寄存的是一个地址,地址表明另一块内存区域,目标自身寄存在那块内存区域中,这边需求的内存空间=目标占用的空间+指针占用的内存空间。

那么假如一个目标自身所需求的空间很小,少于一个指针所需求的空间,这样寄存不就造成了不必要的内存糟蹋了吗,因而,能够将一些内存占用不大的目标(比方NSNumber、NSString、NSDate等)直接存到这个指针的内存空间中,这样就不需求再额定拓荒一个空间来寄存它,需求的内存空间=目标占用的空间,明显的节省了内存空间。

为了区别这个pointer是只用于寄存地址的一般的pointer仍是寄存了其他信息的pointer,就选取其间的某个或某些位作为符号tag,剩下的位就能够用于寄存所需求的数据,叫做payload,这样的指针便是tagged pointer。Objective-C runtime中,isa指针便是运用了tagged pointer来优化了内存。

在”objc-object.h”中,提供了内部接口判别是否是tagged pointer

inline bool
objc_object::isTaggedPointer()

Tagged Pointer的Tag和Payload

一个tagged pointer中,有一位作为mask用来标识是否是tagged pointer,接着是3位寄存tag信息,tag能够取值0-7,假如值是0-6,表明它是一个basic tagged pointer, 而7作为保留值,表明是它是extended tagged pointer

basic tagged pointer中除了1位用于标识是否是tagged pointer,接下来3位寄存目标类型的索引值,剩下60位是payload,用于寄存想要寄存的数据,比方目标的值。

extended tagged pointer中有1位mask和3位tag都取值1,用于表明是extended tagged pointer,接下来8位寄存类型索引值,剩下52位作为payload寄存数据。这样能够寄存多达256种类型,价值是payload从60位削减到52位,能寄存的数据削减了。

“objc-internal.h”中界说了tagged pointer目标类型的枚举:

enum objc_tag_index_t : uint16_t
{
// 60-bit payloads
OBJC_TAG_NSAtom= 0,
OBJC_TAG_1= 1,
OBJC_TAG_NSString= 2,
OBJC_TAG_NSNumber= 3,
OBJC_TAG_NSIndexPath= 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate= 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7= 7,
// 52-bit payloads
OBJC_TAG_Photos_1= 8,
OBJC_TAG_Photos_2= 9,
OBJC_TAG_Photos_3= 10,
OBJC_TAG_Photos_4= 11,
OBJC_TAG_XPC_1= 12,
OBJC_TAG_XPC_2= 13,
OBJC_TAG_XPC_3= 14,
OBJC_TAG_XPC_4= 15,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload= 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload= 263,
OBJC_TAG_RESERVED_264= 264
};

详细哪些位寄存tag和类型索引?

x86_64架构中,是根据LSB最低有用位,寄存在低位:

Objective-C runtime源码小记-Tagged Pointer
Objective-C runtime源码小记-Tagged Pointer

Arm架构中,是根据MSB最高有用位,寄存在高位:

Objective-C runtime源码小记-Tagged Pointer
Objective-C runtime源码小记-Tagged Pointer
为什么ARM上是高位表明,这边触及到了msgSend的一项优化,放在高位能够使msgSend节省一个判别分支。

但是,iOS14后(WWDC2020中介绍),ARM平台上tag信息寄存位做了一些调整,优化了读取二进制文件中的常量数据的内存耗费。详细来说,将3位tag放到了低位,同样的假如tag位位7,也是extended tagged pointer,扩展信息放在紧接着mask位后8位:

Objective-C runtime源码小记-Tagged Pointer

One more thing…

runtime中tagged pointer是被混杂过的,直接读取pointer内部的位得不到正确的信息的,tagged pointer内部完成的详细代码能够检查”objc-internal.h”

// 用于混杂tagged pointer的因子,会被初始化为一个随机数
extern uintptr_t objc_debug_taggedpointer_obfuscator;  
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr);
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr);
static inline bool
_objc_taggedPointersEnabled(void);
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value);
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr);
static inline objc_tag_index_t
_objc_getTaggedPointerTag(const void * _Nullable ptr);
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr);
static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr);

总结:

  • Tagged pointer不是一个正常的指针,是对指针内存空间的最大化运用,运用其间的一些bit位来和一般指针进行区别,剩下的位寄存数据
  • runtime中运用tagged pointer技术寄存一些占用较小的目标,明显的优化了内存
  • runtime中对tagged pointer做了混杂,提升了安全性

参考: alwaysprocessing.blog/2023/03/19/… developer.apple.com/videos/play…