Tagged Pointer - ShenYj/ShenYj.github.io GitHub Wiki

Tagged Pointer

简介

在 2013 年 9 月,苹果推出了 iPhone5s,正式开始切换到了64位指令集,随之为了节省内存和提高执行效率,苹果在优化上引入了 Tagged Pointer(小对象)

  • 由于 NSNumber、NSDate以及部分NSString 等类的变量本身的值需要占用的内存大小常常不需要 8 个字节

  • 苹果将Tagged Pointer引入,给 64 位系统带来了内存的节省和运行效率的提高。Tagged Pointer通过在其最后一个 bit 位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其 isa 变量

  • 在objc底层源码中也有很多关于Tagged Pointer特殊处理的地方,比如set、get方法中会忽略对小对象的retain、release等操作,这也是为什么同样是字符串,为什么有些异步频繁操作会造成crash,而一些小对象字符串不会,就是因为异步过多release导致

探索

小对象真实类型可在objc-internal.h源码中定义的枚举类型了解到

#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
{
    // 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_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

通过内置函数

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

decode 出对应的objc_tag_index_t值, 即可验证其类型

e.g.

/// 需要引入runtime库 `#import <objc/runtime.h>`
NSString *str = [NSString stringWithFormat:@"b"];
NSLog(@"0x%lx", _objc_decodeTaggedPointer(str));

/// log
0xa000000000000621

结果分析

  • decode得出结果0xa000000000000621
  • 十六进制转成二进制得到 0xa -> 1010
  • 1010 最高位有值,代表其是Tagged Pointer类型
  • 1010 去掉最高位的剩余三位 -> 010 则代表其真实类型
  • 010 对应的值是 2 -> OBJC_TAG_NSString = 2

Tagged Pointer 类型检查

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    // #   define _OBJC_TAG_MASK (1UL<<63)
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

代码分析

  • _OBJC_TAG_MASK:1左移63位 -> 保留最高位的值
  • (uintptr_t)ptr & _OBJC_TAG_MASK) -> 取高位
  • ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK -> 判断高位是否有值

当前源码版本objc4-781

818.2 源码变化

翻看了目前最新的源码 objc4-818.2

  • _objc_isTaggedPointer 函数下新增了一个 _objc_isTaggedPointerOrNil 函数

    static inline bool
    _objc_isTaggedPointerOrNil(const void * _Nullable ptr)
    {
        // this function is here so that clang can turn this into
        // a comparison with NULL when this is appropriate
        // it turns out it's not able to in many cases without this
        return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
    }
  • 关于 _OBJC_TAG_MASK

    之前的版本只是通过条件编译判断了下是 macOS 平台,在最新的 818.2 源码中新增对 arm64 的新标记
    _OBJC_TAG_MASK 的值最终对我们 iOS 平台并没有造成什么改变, 还是通过最高位判断

    • OBJC_SPLIT_TAGGED_POINTERSOBJC_MSB_TAGGED_POINTERS

      #if __arm64__
      // ARM64 uses a new tagged pointer scheme where normal tags are in
      // the low bits, extended tags are in the high bits, and half of the
      // extended tag space is reserved for unobfuscated payloads.
      #   define OBJC_SPLIT_TAGGED_POINTERS 1
      #else
      #   define OBJC_SPLIT_TAGGED_POINTERS 0
      #endif
      
      #if (TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __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
    • _OBJC_TAG_MASK

      #if OBJC_SPLIT_TAGGED_POINTERS
      #   define _OBJC_TAG_MASK (1UL<<63)
      #elif OBJC_MSB_TAGGED_POINTERS
      #   define _OBJC_TAG_MASK (1UL<<63)
      #else
      #   define _OBJC_TAG_MASK 1UL
      #endif

学习资料

两篇文章内容一样,文章中详细的介绍了 Tagged Pointer的优点和注意事项

⚠️ **GitHub.com Fallback** ⚠️