Tagged Pointer
本文由 简悦 SimpRead 转码, 原文地址 roadmap.isylar.com
Tagged Pointer 是苹果在 64bit 设备提出的一种存储小对象的技术,它具有以下特点
Tagged Pointer 是苹果在 64bit 设备提出的一种存储小对象的技术,它具有以下特点
- Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。
- 它的内存并不存储在堆中,也不需要 malloc 和 free,不走引用计数那一套逻辑,由系统来处理释放
- 可以通过设置环境变量
OBJC_DISABLE_TAGGED_POINTERS
来有开发者决定是否使用这项技术
从 32 位迁移到 64 位 CPU,逻辑上虽然不会有任何变化,但是所占有的内存空间却会翻倍。下面以 NSNumber 对象为例,大家可以清晰看出 NSNumber 对象在内存空间上的变化情况:
源码
// 64-bit Mac - tag bit is LSB
// Everything else - tag bit is MSB
// array slot includes the tag bit itself
// array slot has no extra bits
定义了很多位信息,我们需要关注的几个:
- _OBJC_TAG_MASK :标记位标记该指针是否是 tagged pointer
- _OBJC_TAG_INDEX_MASK :tag 的值是 7 表示有扩展的 tag 位
- 其他的都是一些定义,用来通过位运算来获取 tag 的值、ext tag 的值的 mask 以及一些其他的左移右移位
如何判断是 tagged pointer
有一个标记位来标识指针是否是 tagged pointer 的
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
通过位运算获取标识位的值来确定是否是 tagged pointer;需要留意的是不同的架构标记位不太一样,有的是用最低位、有的使用最高位。
系统对 tagged pointer 的加密
在 iOS12 系统之前,发现是可以直接打印 tagged pointer 的值的,可读性非常好,但是 12 之后再打印就发现完全看不懂了。
- (void)testCase {
NSString *stringWithFormat1 = [NSString stringWithFormat:@"y"];
[self formatedLogObject:stringWithFormat1];
}
- (void)formatedLogObject:(id)object {
if (@available(iOS 12.0, *)) {
NSLog(@"%p %@ %@", object, object, object_getClass(object));
} else {
NSLog(@"0x%6lx %@ %@", object, object, object_getClass(object));
}
}
复制代码
上面的测试代码,在 12 之前输出: 0x79 是 ASCII 对应的 y 字符的值
0xa000000000000791 y NSTaggedPointerString
iOS12 之后输出:
0xcb47b8d98a2fa15f y NSTaggedPointerString
iOS12 之前打印指针的值能很清晰的看到数据等信息,iOS12 之后系统则打印的完全看不懂了,看了源代码发现苹果是做了混淆,让我们不能直接得到值,从而避免我们去很容易就伪造出一个 tagged pointer 对象
Tagged Pointer 对象
系统通过 3bit 的标记位来标识 tagged pointer 对象的类,它的定义在objc_tag_index_t
中 比如 2 表示是 NSString、6 表示是 NSDate,我们知道 3bit 能表示的最大值是 7,这个 7 系统用来预留,用来标记是否有额外的标记位,这样就能支持更多的类支持 tagged pointer
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
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_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_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
结论
1、Tagged Pointer 有长度限制,过长会依然会采用对象的形式保存
2、Tagged Pointer 没有 isa 指针,它不是一个对象,只是一个伪装成对象的普通变量而已。
3、Tagged Pointer 是一个特殊的指针,不指向任何实质地址。