本文由 简悦 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 是一个特殊的指针,不指向任何实质地址。

Reference

1.iOS 特有概念 TaggedPointer

2.OC 内存管理 - Tagged Pointer 初探