Tagged pointer

262 阅读3分钟

背景

在iPhone5s之前,iPhone设备都是采用32位架构的处理器,5s的时候,苹果采用 64 位架构的 A7 双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged pointer的概念

如果我们要存储一个NSNumber类型的对象,值为整数,NSInteger类型的变量在32位cpu下占4字节,64位cpu下占用8字节,指针类型的大小也是和cpu有关,32 位 cpu 下为 4 个字节,在 64 位 cpu 下也是 8 个字节

所以一个iOS程序,从32位迁移到64位,如果没有Tagged pointer时对象所占用的内存会增大很多。同时从效率角度来看,一个 NSNumber 对象,在堆上分配内存,维护引用计数,管理生命期都是比较消耗性能的

Tagged Pointer

  • Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate,NSString,NSIndexPath
  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free
  • 在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。

Tagged Pointer 探究

判断指针变量是否为Tagged Pointer 类型

// objc4-838.1   objc_object.h
inline bool
objc_object::isTaggedPointer() 
{
    return  _objc_isTaggedPointer(this);
}

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

// _OBJC_TAG_MASK
#if OBJC_SPLIT_TAGGED_POINTERS    //ARM64 处理器
#   define _OBJC_TAG_MASK (1UL<<63)
#elif OBJC_MSB_TAGGED_POINTERS    // MSB
#   define _OBJC_TAG_MASK (1UL<<63)
#else  // LSB
#   define _OBJC_TAG_MASK 1UL
#endif

我们注意到在不同的系统下,掩码_OBJC_TAG_MASK的定义有所不同:

在iOS系统下(__x86_64__),_OBJC_TAG_MASK1UL<<63,等于是8字节地址的最高有效位1;

在macOS系统下,_OBJC_TAG_MASK1UL,等于是8字节地址的最低有效位1;

lsb和msb的区别如下:

  • MSB:最高位(Most Significant Bit)指二进制序列。
  • LSB:最低位(Least Significant Bit)指二进制序列。

通过上面代码,我们可以说在不同的64位设备上可以通过判断指针值的最高位或最低位是否为1,来判断指针是否是Tagged Pointer

存储结构

/***********************************************************************
* Tagged pointer objects.
*
* Tagged pointer objects store the class and the object value in the 
* object pointer; the "pointer" does not actually point to anything.
* 
* Tagged pointer objects currently use this representation:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  tag index
* 60 bits  payload
* (MSB)
* The tag index defines the object's class. 
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an 
* "extended" representation, allowing more classes but with smaller payloads:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  0b111
*  8 bits  extended tag index
* 52 bits  payload
* (MSB)
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/

我们现在只讨论64位下小端模式的存储

ded32ce46106f399ed0b5366af217993.webp

如上图,最高位存储的是tagged pointer的标志位,然后3位来存储tag indextag index定义了当前对象的类型。 最后4位代表数据类型

类标识

// objc-internal.h  
enum
{
    // 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,
    ...
};

数据类型(为字符串时,标识字符串长度)

截屏2022-05-14 10.44.31.png

:在看标志位的时候,关闭混淆,在源码中发现地址和objc_debug_taggedpointer_obfuscator进行了异或,所以打印的地址不是我们需要的,需要设置 OBJC_DISABLE_TAG_OBFUSCATION:YES Xnip2022-05-14_09-51-15.jpg

代码演示

Xnip2022-05-14_10-49-48.jpg