背景
在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_MASK是1UL<<63,等于是8字节地址的最高有效位1;
在macOS系统下,
_OBJC_TAG_MASK是1UL,等于是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位下小端模式的存储
如上图,最高位存储的是tagged pointer的标志位,然后3位来存储tag index,tag 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,
...
};
数据类型(为字符串时,标识字符串长度)
注:在看标志位的时候,关闭混淆,在源码中发现地址和objc_debug_taggedpointer_obfuscator进行了异或,所以打印的地址不是我们需要的,需要设置 OBJC_DISABLE_TAG_OBFUSCATION:YES