Tagged Pointer小记

本文使用的测试环境是arm64架构真机

为了探究Tagged Pointer本质,可以查看runtime源码,主要看文件objc-internal.h

宏定义

可以看到以下宏定义,只有在64位系统才支持Tagged Pointer

1
2
3
#if __LP64__
#define OBJC_HAVE_TAGGED_POINTERS 1
#endif

64-bit的mac,tag存储在LSB(Least Significant Bit 最低位)。其它情况比如64位的真机和模拟器,tag存储在MSB(Most Significant Bit 最高位)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
# define _OBJC_TAG_INDEX_SHIFT 60
# define _OBJC_TAG_SLOT_SHIFT 60
# define _OBJC_TAG_PAYLOAD_LSHIFT 4
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
# define _OBJC_TAG_EXT_INDEX_SHIFT 52
# define _OBJC_TAG_EXT_SLOT_SHIFT 52
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
# define _OBJC_TAG_MASK 1UL
# define _OBJC_TAG_INDEX_SHIFT 1
# define _OBJC_TAG_SLOT_SHIFT 0
# define _OBJC_TAG_PAYLOAD_LSHIFT 0
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
# define _OBJC_TAG_EXT_MASK 0xfUL
# define _OBJC_TAG_EXT_INDEX_SHIFT 4
# define _OBJC_TAG_EXT_SLOT_SHIFT 4
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif

接下来是一个枚举定义,定义了默认的使用Tagged Pointer的类。例如NSString、NSNumber、NSIndexPath、NSDate(OBJC_TAG_NSAtom、OBJC_TAG_1、OBJC_TAG_NSManagedObjectID不知道是啥意思,还请知道的同学告诉我)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum objc_tag_index_t : uint16_t
enum
{
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,
OBJC_TAG_RESERVED_7 = 7,

OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,

OBJC_TAG_RESERVED_264 = 264
};

方法定义

判断是不是Tagged Pointer

1
2
3
4
5
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

生成一个Tagged Pointer,最高的4位是tagged,剩下的是数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
if (tag <= OBJC_TAG_Last60BitPayload) {
return (void *)
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
} else {
return (void *)
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
}
}

Tagged Pointer中取出值

1
2
3
4
5
6
7
8
9
10
11
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{
// assert(_objc_isTaggedPointer(ptr));
uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return ((uintptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return ((uintptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}

NSNumber应用举例

可以使用下面代码来验证NSNumber如何使用Tagged Pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSNumber *charNumber = [NSNumber numberWithChar:'1'];
NSNumber *shortNumber = [NSNumber numberWithShort:1];
NSNumber *intNumber = [NSNumber numberWithInt:1];
NSNumber *floatNumber = [NSNumber numberWithFloat:1.0];
NSNumber *longNumber = [NSNumber numberWithLong:1];
NSNumber *doubleNumber = [NSNumber numberWithDouble:1.0];

// 输出变量的指针地址:
// charNumber 0xb000000000000310
// shortNumber 0xb000000000000011
// intNumber 0xb000000000000012
// floatNumber 0xb000000000000014
// longNumber 0xb000000000000013
// doubleNumber 0xb000000000000015

不难发现规律,都是以b(1011)开头

  • 最高位是1,说明这个指针是一个Tagged Pointer
  • 第61-63位是11(十进制是3),也就是OBJC_TAG_NSNumber(查上面的枚举)
  • 第1-4位是NSNumber的类型:比如,char是0、short是1、int是2、float是4
  • 剩下的56位就是真正的值了

NSString应用举例

1
2
3
4
5
6
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"ab"];

// 输出变量的指针地址:
// str1: 0xa000000000000611
// str2: 0xa000000000062612

与NSNumber类似

  • 最高位是1,说明这个指针是一个Tagged Pointer
  • 第61-63位是11(十进制是2),也就是OBJC_TAG_NSString
  • 第1-4位是字符串长度
  • 剩下的56位就是真正的值了

更多细节推荐这篇文章采用Tagged Pointer的字符串


参考文章