产生背景

在项目开发中,我们总是希望能够节约空间,减少不必要的系统开销,或者提升效率,缩短执行时间,当然有时候也会通过增加空间开销来换取执行时间的缩减。可以为了方便CPU寻址提升存取效率,多数情况下内存地址会进行对齐处理,对象地址会是指针的整数倍,所以在小数据存储上,会有很多空间并没有存储数据但是却不得不空出来.指针大小与CPU位数有关,在32位CPU上占据4个字节,在64位CPU上占据8个字节。通常情况下,存储数据时会先开辟空间存储数据指针,然后再开辟另外的空间去存储真正的对象,同时将对象指针指向该内存空间。而事实上,有些基本的数据类型,在大多数情况下占用的内存空间很小,甚至连对象指针的空间都用不完,比如OC中的NSNumber, NSNumber,int,NSIndexPath等。以NSNumber类型保存整数为例,在32bit操作系统上,一个四字节的空间可以存储的最大整数为2^31=2147483648,已经达到了21亿多,而实际上我们使用的整数很难达到这样的级别,这个存储用的四字节里将有很多空余出来的空间被浪费掉,如果这时再开辟一个指针空间来指向这个空间,那么存储这个NSnumber类型的数据就需要额外的四个字节。在不改变代码实现逻辑的情况下,假设从在32位机器上移植到64位机器上去运行,由于CPU位数的关系,对象所占据的内存空间会需要增加一倍,如果这样的对象数量非常庞大,对内存消耗和存取效率来说都是极大的浪费。

所以为了改进上面提到的内存占用和效率问题,apple在iOS的64位处理器上提出了一种提升性能的解决方案-------tagged pointer.简单来说,对于某些占用内存很小的数据实例,不再单独开辟空间去存储,而是将实际的实例值存储在对象的指针中,同时对该指针进行标记,用于区分正常的寻址指针。此时的指针就变成了tag+data的伪指针,同时包含了标记和真实数据而不再作为真实地址进行寻址使用。

根据官方的说明,使用tagged pointer进行小数据存储的优势非常明显:

  • 可以见上一半的内存占用;
  • 可以将访问速度提升3倍以上;
  • 提升100倍的创建销毁速度.

实现原理

我们在objc-750里找到了如下的说明(objc-runtime-new.mm  6694-6723),

* 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.

从apple给出的声明中,可以得到:

(1) 标签指针对象存储了类信息和对象实际的值,此时的指针不指向任何东西;

(2) 使用最低位作为标记位,如果是标签指针对象就标记为1,如果是普通对象类型就标记为0;

(3) 紧接着三位是标签索引位;

(4) 剩余的60位位有效的负载位,标签索引位定义了标签对象代表的对象的真实类型,负载的格式由实际的类定义;

(5) 如果标签位是0b111,表示该对象使用了是被扩展的标签对象,这种扩展的方式可以运训更多的类使用标签对象来表示,同时负载的有效位数变小。这时:

5.1 最低位是标记位;

5.2 紧接着三位位0b111;

5.3 紧接着八位位扩展的标记位;

5.4 剩余的52位才是真正的有效的负载位。

(6) 并不是所有的架构中都使用低位做标记位.在指令集框架中,除了64-bit的Mac操作系统之外,其余全是使用MSB。比如iOS就是使用高位作为标志位,而在接下来的讨论中,仅以iOS使用高位作为标记位的实现策略来讨论标签指针。

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __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

在iOS中,使用最高位作为标签指针标志位,接下来的三位来作为类标记位,且当这三位为111的时候标签指针就变成了扩展的标签指针,所以普通的标签指针最多能表示7(000-110)种对象类型,那这些对象类型都是那些呢(objc4/runtime/objc-internal.h 240-269)?

{// 60-bit payloadsOBJC_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 reservedOBJC_TAG_RESERVED_7        = 7, // 52-bit payloadsOBJC_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_First60BitPayload = 0, OBJC_TAG_Last60BitPayload  = 6, OBJC_TAG_First52BitPayload = 8, OBJC_TAG_Last52BitPayload  = 263, OBJC_TAG_RESERVED_264      = 264
};

从上边的定义中,我们可以看到最开头的七种枚举就是最原始的标签指针对象所能代表的六个类原型。而我们比较常见的就是2(NSString), 3(NSNumber), 4(NSIndexPath)和6(NSDate)这四种类型,我们简单使用NSString来做验证.

实现验证

  • 准备工作

在此之前我先来看一下tagged pointer的初始化过程(objc-interal.h 388-412):

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{// PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.// They are reversed here for payload insertion.// assert(_objc_taggedPointersEnabled());if (tag <= OBJC_TAG_Last60BitPayload) {/*当tag小于等于6(110)时,是taggedPointer所能表示的基本类型,即: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,   */// assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);/*_OBJC_TAG_MASK:标签指针标记位(是否是标签指针)(uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT):标签指针的类型标记(是标签指针中何种类型)(value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT):真实的标签指针的值*/uintptr_t result =(_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));return _objc_encodeTaggedPointer(result);} else {/*当tag小于等于6(111)时,是taggedPointer所能表示的延展类型*/// assert(tag >= OBJC_TAG_First52BitPayload);// assert(tag <= OBJC_TAG_Last52BitPayload);// assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);uintptr_t result =(_OBJC_TAG_EXT_MASK |((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));return _objc_encodeTaggedPointer(result);}
}

在上述的实现中,标签指针对真实的结果进行了一次encode操作,将实际结果与objc_debug_taggedpointer_obfuscator进行异或操作得到(objc-internal.h 375-380),

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

所以查看标签指针时首先需要进行decode操作(只需要再次与objc_debug_taggedpointer_obfuscator进行异或操作即可)

[使用同一个随机数两次对某一个数字异或操作可以得到原来的数字值]

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

可以看到,在Mac10.14和iOS12之前,对taggedpointer做异或的objc_debug_taggedpointer_obfuscator值为0,之后是一个无符号长整型的随机数 (objc-runtime-new.mm 6835-6849),

static void
initializeTaggedPointerObfuscator(void)
{if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||// Set the obfuscator to zero for apps linked against older SDKs,// in case they're relying on the tagged pointer representation.DisableTaggedPointerObfuscation) {objc_debug_taggedpointer_obfuscator = 0;} else {// Pull random data into the variable, then shift away all non-payload bits.arc4random_buf(&objc_debug_taggedpointer_obfuscator,sizeof(objc_debug_taggedpointer_obfuscator));
//既然objc_debug_taggedpointer_obfuscator是个随机数,下边的操作除了混淆之外应该没什么作用objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;}
}

所以,如果想要得到真实的标签指针的结果,需要对实际的指针值进行解码(也就是与该随机数objc_debug_taggedpointer_obfuscator再进行一个异或运算),但是这个随机内敛函数并没有公开,不过我们知道它一定存在,所以我们可以使用extern关键字引用变量,同时自己实现decode操作。

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

那么如何判断一个指针是否是taggedPointer呢?同样在objc-internal.h 419-423中找到了如下实现:

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

由于该实现依赖了_OBJC_TAG_MASK,所以将源码中的相关宏定义也需要定义一下(objc-internal.h 330-336):

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __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#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

一般情况下这个优化特性在环境允许时时开启的,但是由于是可以关闭的(objc-runtime-new.mm 7247-7261),

static void
disableTaggedPointers()
{objc_debug_taggedpointer_mask = 0;objc_debug_taggedpointer_slot_shift = 0;objc_debug_taggedpointer_slot_mask = 0;objc_debug_taggedpointer_payload_lshift = 0;objc_debug_taggedpointer_payload_rshift = 0;objc_debug_taggedpointer_ext_mask = 0;objc_debug_taggedpointer_ext_slot_shift = 0;objc_debug_taggedpointer_ext_slot_mask = 0;objc_debug_taggedpointer_ext_payload_lshift = 0;objc_debug_taggedpointer_ext_payload_rshift = 0;
}

所以在使用前做一个安全判断:

static inline bool
_objc_taggedPointersEnabled(void)
{extern uintptr_t objc_debug_taggedpointer_mask;return (objc_debug_taggedpointer_mask != 0);
}

自此,验证所需要的环境已经搭建完成.

  • 开始验证

使用NSNumber和NSString两个常见类型来做简单验证,需要注意是:

  1. tagged pointer是一个64bit操作系统的优化方案,所以请使用x86_64或者arm64指令集进行验证.以下验证中使用x86_64指令集进行验证
  2. 在使用x86_64指令集时可能会出现.
dyld: Symbol not found: _objc_debug_taggedpointer_obfuscator

这是由于操作系统版本过高与runtime不兼容导致,使用较高版本的模拟器即可解决.

NSNumber类型

首先分别定义char,short, int, long,float, double基本类型,然后转化为NSNumber对象,decode之后查看对应tagged pointer实际值:

    if (_objc_taggedPointersEnabled()) {char    num0 = 12;short   num1 = 3;int     num2 = 15;long    num3 = 118;float   num4 = 11.0;double  num5 = 10.0;NSNumber *number0 = @(num0);NSNumber *number1 = @(num1);NSNumber *number2 = @(num2);NSNumber *number3 = @(num3);NSNumber *number4 = @(num4);NSNumber *number5 = @(num5);NSNumber *number6 = @(MAXFLOAT);NSArray<NSNumber *> *array = @[number0, number1, number2, number3, number4, number5, number6];for (NSNumber *num in array) {BOOL isTaggedPointer = _objc_isTaggedPointer((__bridge const void *) num);if (isTaggedPointer) {NSLog(@"%@是标签指针, realValue == 0x%lx, 类名:%@", num, _objc_decodeTaggedPointer((__bridge const void *)num), [num class]);} else {NSLog(@"%@不是标签指针, address == 0x%p, 类名:%@", num, num, [num class]);}}} else {NSLog(@"tagged pointer 特性不可用");}

输出结果:

12是标签指针, realValue == 0xb0000000000000c0, 类名:__NSCFNumber
3是标签指针, realValue == 0xb000000000000031, 类名:__NSCFNumber
15是标签指针, realValue == 0xb0000000000000f2, 类名:__NSCFNumber
118是标签指针, realValue == 0xb000000000000763, 类名:__NSCFNumber
11是标签指针, realValue == 0xb0000000000000b4, 类名:__NSCFNumber
10是标签指针, realValue == 0xb0000000000000a5, 类名:__NSCFNumber
3.402823e+38不是标签指针, address == 0x0x600003ff2980, 类名:__NSCFNumber

多次执行之后结果不会发生变化。最高四位0xb=1011,其中最高位为1,表明该指针并不是真正的对象指针,而是一个标签指针;紧接着三位是3(011),对应的value的实际类型为NSNumber类型,中间52位为实际负载值,而最后四位则更加具体地表明了实际类型:0表示char类型,1表示short类型,2表示整形,3表示长整型,4表示单精度类型,5表示双精度类型.

而需要注意的是,之所以采用了标签指针来做小数据的存储是因为对象指针可以完整表示标签+实际值组合,如果这个组合的实际值超出了对象指针可以表示的范围,还是要使用真正的对象指针来表示实际的对象空间。所以变量number6并不是标签指针,而是使用了普通对象开辟新的地址空间来进行存储.

NSString类型

if (_objc_taggedPointersEnabled()) {NSMutableString *str = [NSMutableString string];char ch = 'a';NSString *immutable = nil;for(int i = 0; i < 20; i++) {[str appendFormat:@"%c", ch++];immutable = [str copy];BOOL isTaggedPointer = _objc_isTaggedPointer((__bridge const void *)immutable);if (isTaggedPointer) {NSLog(@"%@是标签指针, realValue == 0x%lx", immutable, _objc_decodeTaggedPointer((__bridge const void *)immutable));} else {NSLog(@"%@不是标签指针, address == %p", immutable, immutable);}}
} else {NSLog(@"tagged pointer 特性不可用");
}

输出:

a是标签指针, realValue == 0xa000000000000611
ab是标签指针, realValue == 0xa000000000062612
abc是标签指针, realValue == 0xa000000006362613
abcd是标签指针, realValue == 0xa000000646362614
abcde是标签指针, realValue == 0xa000065646362615
abcdef是标签指针, realValue == 0xa006665646362616
abcdefg是标签指针, realValue == 0xa676665646362617
abcdefgh是标签指针, realValue == 0xa0022038a0116958
abcdefghi是标签指针, realValue == 0xa0880e28045a5419
abcdefghij不是标签指针, address == 0x28148f020
abcdefghijk不是标签指针, address == 0x2814867e0
abcdefghijkl不是标签指针, address == 0x28148f020
abcdefghijklm不是标签指针, address == 0x28148ede0
abcdefghijklmn不是标签指针, address == 0x2814867e0
abcdefghijklmno不是标签指针, address == 0x281ab7d80
abcdefghijklmnop不是标签指针, address == 0x281ab7a20
abcdefghijklmnopq不是标签指针, address == 0x281ac9f20
abcdefghijklmnopqr不是标签指针, address == 0x281ac9cb0
abcdefghijklmnopqrs不是标签指针, address == 0x281ac9f20
abcdefghijklmnopqrst不是标签指针, address == 0x281ac9cb0

在字符数目小于9之前,高四位a=0b1010,最高位位1说明这是一个标签指针,紧接着三位为010=2,根据枚举类型可知,2表示字符串类型.

OBJC_TAG_NSString          = 2, 

在上述验证中发现:

  • 在字符数目大于9之后,不再使用标签指针进行表示,而是使用正常的对象指针进行存储;
  • 字符个数不大于7时,使用标签指针进行表示:
    • 通过字符串中字符的Ascall码来进行依次存储的;
    • 使用两位十六进制的数据来表示一个字符;
    • 最后四位用来表示字符串中的字符个数(对于NSNumber类型这四位用来表示具体的数据类型,而NSString类型表示字符串中的字符个数)
  • 当字符个数大于7小于9时,依然使用标签指针进行表示,但是存储方法发生了变化:
    • 由于高四位做标记位,低四位存储字符个数,只剩下56位可用,一个字符占据1个字节即8位,所以理论上按照ascall存储的话最多只能存储7个字符,此时不能再使用Ascall码进行表示;
    • 此时采用了一种压缩算法来进行字符存储使得taggedPointer可以表示的字符长度达到了9个,这也说明apple对于极致的追求,即使只扩展了两个字符,还是会努力去做出优化.毕竟taggedPointer在访问速度以及存储开销上的提升不是一点点.

而对于压缩算法实现,apple对于常用字符的使用频率做个大数据的统计,然后给出了一个最常用字符的字符集表(以下称"字符表"),然后使用5bit或者6bit来表示一个字符,而不再使用8bit来表示,这样就可以使用剩余的56bit来存储更多的字符.使用的字符集为:

eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX

共有64个字符,只需要6bit即可表示一个字符即可完全表示该表中的任何一个字符.例如字符'e'可以使用0(0b000000)来表示,字符'i'可以使用1(0b000001),字符'a'可以使用8(0b001000),字符' '空格可以使用15(0b001111)来表示等.

当使用表示字符串的tagged pointer来存储字符时,有以下三种情况:

  • 如果字符数量不大于7,则使用ascall码依次存储;
  • 当字符数量为8或者9时且字符串的所有字符都出现字符表中时,使用6bit编码依照新的字符集(eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX)进行存储;
  • 当字符数量为10或者11且字符串的字符都出现在常用字符标中前32个位置时,使用5bit编码按照新的字符集(eilotrm.apdnsIc ufkMShjTRxgC4013)进行存储,否则使用开辟地址进行存储.

所以对于以上验证中的

abcdefgh是标签指针, realValue == 0xa0022038a0116958
abcdefghi是标签指针, realValue == 0xa0880e28045a5419

可以做如下解码:

在0xa0022038a0116958中,
前4bit是标记位:0xa=0b1010第一位用来表示是标签指针,0b010表示实际类型为2,即字符串类型;
最后一个4bit表示字符串的长度,8表示该字符串一共有8个字符;
中间的56bit转化为二进制字符:
0x0022038a011695=0b001000 100000 001110 001010 000000 010001 011010 010101
然后每六位转化为10进制整数并在新的字符标中查询可得:= 8       32     14     10     0      17     26     21
index=8在新字符标中的字符为: a
index=32在新字符标中的字符为:b
index=14在新字符标中的字符为:c
index=10在新字符标中的字符为:d
index=0在新字符标中的字符为: e
index=17在新字符标中的字符为: f
index=26在新字符标中的字符为: g
index=21在新字符标中的字符为:h
所以0x0022038a011695表示的字符串为:abcdefgh同样,
0x0880e28045a541=0b001000 100000 001110 001010 000000 010001 011010 010101 000001=  8      32     14     10     0       17    26     21     1=  a      b      c      d      e       f     g      h      i

所以综上所述,tagged pointer表示字符串时,最多可以表示11个字符。或许这里会有一个疑问,既然tagged pointer可以表示10位或者11的字符串,那为什么刚才的验证中字符数目超过9之后字符串没有使用tagged pointer表示,而是成为了真正的对象呢?

事实上,当字符数量为10或者11时,只能用5bit来表示一个字符,所以最多只能表示32个字符,而不再是64个字符,所以只有当

  • 字符串中的数量为10或者11
  • 字符串中每一个字符都在前32个字符中时

同时满足这两个条件时,tagged pointer才可以表示10个或者11字符长度的字符串.简单验证一下:

        NSString *str = [NSString stringWithFormat:@"%@", @"eiloapdnIcR"];BOOL isTaggedPointer = _objc_isTaggedPointer((__bridge const void *)str);if (isTaggedPointer) {NSLog(@"%@是标签指针:%@, realValue == 0x%lx", str, object_getClass(str), _objc_decodeTaggedPointer((__bridge const void *)str));} else {NSLog(@"%@不是标签指针, address == %p", str, str);}

输出结果:

eiloapdnIcR是标签指针:NSTaggedPointerString, realValue == 0xa00221a12a5b5d8b

其他类型

常用的标签指针还有NSIndexPath以及NSDate等,使用同样的方法进行验证.

NSIndexPath

if (_objc_taggedPointersEnabled()) {NSIndexPath *indexPath01 = [NSIndexPath indexPathForRow:1 inSection:15];NSIndexPath *indexPath02 = [NSIndexPath indexPathForRow:2 inSection:15];NSIndexPath *indexPath03 = [NSIndexPath indexPathForRow:3 inSection:15];NSIndexPath *indexPath04 = [NSIndexPath indexPathForRow:15 inSection:16*16*16 -1];NSIndexPath *indexPath05 = [NSIndexPath indexPathForRow:15 inSection:16*16*16];NSIndexPath *indexPath06 = [NSIndexPath indexPathForRow:15 inSection:2*16*16*16];NSIndexPath *indexPath07 = [NSIndexPath indexPathWithIndex:3];NSIndexPath *indexPath08 = [NSIndexPath indexPathWithIndex:5];NSIndexPath *indexPath09 = [NSIndexPath indexPathForRow:0 inSection:3];NSIndexPath *indexPath10 = [NSIndexPath indexPathForRow:3 inSection:0];NSArray<id> *array = @[indexPath01, indexPath02, indexPath03, indexPath04, indexPath05, indexPath06, indexPath07, indexPath08, indexPath09, indexPath10];for (id ele in array) {BOOL isTaggedPointer = _objc_isTaggedPointer((__bridge const void *)ele);if (isTaggedPointer) {NSLog(@"%@是标签指针, realValue == 0x%lx, 类名:%@", ele, _objc_decodeTaggedPointer((__bridge const void *)ele), [ele class]);} else {NSLog(@"%@不是标签指针, address == 0x%p, 类名:%@", ele, ele, [ele class]);}}
}

输出结果:

<NSIndexPath: 0xc87e22c9e7f07fd0> {length = 2, path = 15 - 1}是标签指针, realValue == 0xc000000000200f16, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7907fd0> {length = 2, path = 15 - 2}是标签指针, realValue == 0xc000000000400f16, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7b07fd0> {length = 2, path = 15 - 3}是标签指针, realValue == 0xc000000000600f16, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e63f8fd0> {length = 2, path = 4095 - 15}是标签指针, realValue == 0xc000000001efff16, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e62070d0> {length = 2, path = 4096 - 15}是标签指针, realValue == 0xc000000001f00016, 类名:NSIndexPath
<NSIndexPath: 0x600002c8aa60> {length = 2, path = 8192 - 15}不是标签指针, address == 0x0x600002c8aa60, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7d073c8> {length = 1, path = 3}是标签指针, realValue == 0xc00000000000030e, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7d075c8> {length = 1, path = 5}是标签指针, realValue == 0xc00000000000050e, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7d073d0> {length = 2, path = 3 - 0}是标签指针, realValue == 0xc000000000000316, 类名:NSIndexPath
<NSIndexPath: 0xc87e22c9e7b070d0> {length = 2, path = 0 - 3}是标签指针, realValue == 0xc000000000600016, 类名:NSIndexPath

可以大致看出:

  • tag index为0xc(0b1100最高位1表示标签指针,低三位为4表示NSIndexPath类型);
  • 在单节点NSIndexPath标签指针中:
    • 最后八位应该是标记位,使用0x0e表示单节点类型;
    • 除去最高位的4位为tag index,最低八位表示表示具体单节点类型其余52位为负载.
  • 在双节点的NSIndexPath标签指针中:
    • 最后八位应该是标记位,使用0x16表示双节点类型;
    • 除去最高4位为tag index,最低八位表示具体双节点类型,其余52位为负载;
    • 在52位负载中,紧挨着标记位的12位表示section,可以表示的值不超过16^3-1;其余四十位表示Row,但是存储的并不是真实的Row值,而是Row的2倍.
  • NSIndexPath *indexPath01 = [NSIndexPath indexPathForRow:1 inSection:15];realValue == 0xc000000000200f16
    其中:0xc是标签指针的标记索引位,0x0000000002,表示row值,由于保存时*2,所以真实的row值= 0x0000000002/2=0x0000000001;0x00f,表示section值15,0x16,类型标记,表示双节点NSIndexPath类型NSIndexPath *indexPath04 = [NSIndexPath indexPathForRow:15 inSection:16*16*16 -1];
    realValue == 0xc000000001efff16
    其中:0xc是标签指针的标记索引位,000000001e,表示row值,由于保存时*2,所以真实的row值= 000000001e/2=0x000000000f;0xfff,表示section值16^3-1,0x16,类型标记,表示双节点NSIndexPath类型

    当时当section=16^3时,低12位并不足以表示,所以此时才用了向高位进位的办法,由于原始的高位使用row*2来进行存储所以此时高位就变成了奇数.

  • NSIndexPath *indexPath05 = [NSIndexPath indexPathForRow:15 inSection:16*16*16];
    realValue == 0xc000000001f00016
    其中:0xc为标签标记位;0x000000001f=31,由于row在存储时使用了row*2进行值存储,所以30是row的存储值,row=30/2=15.而剩余的1是section的进位;0x000是section进位之后的值,section= 0x000+1*16^3所以可以预知,在row=15, section=16^3+1时,对应的realValue= 0xc000000001f00116

    所以在64bit操作系统使用MBS表示的标签类型指针中,可表示的NSIndexPath的section最大值为2*16^3.当section超过这个范围时(或者row*2超过40bit所能表示的范围),标签指针不能正确低表示NSIndexPath,系统就会使用正常的对象表示NSIndexPath.

NSDate

NSDate的存储比较复杂,只做简单验证.

    if (_objc_taggedPointersEnabled()) {NSDate *date01 = [NSDate dateWithTimeIntervalSince1970:-1];NSDate *date02 = [NSDate dateWithTimeIntervalSince1970:0];NSDate *date03 = [NSDate dateWithTimeIntervalSince1970:1];NSDate *date04 = [NSDate dateWithTimeIntervalSince1970:0xd27e440000000 / 0x800000 + 0x20000000000000 / 0x1000000];NSDate *date05 = [NSDate dateWithTimeInterval:2 sinceDate:date04];NSDate *date06 = [NSDate date];NSArray<NSDate *> *array = @[date01,date02,date03,date04,date05,date06];for(NSUInteger i = 0; i < array.count; i++) {NSDate *ele = array[i];BOOL isTaggedPointer = _objc_isTaggedPointer((__bridge const void *)ele);if (isTaggedPointer) {NSLog(@"%@是标签指针, realValue == 0x%lx, 类名:%@", ele, _objc_decodeTaggedPointer((__bridge const void *)ele), [ele class]);} else {NSLog(@"%@不是标签指针, address == 0x%p, 类名:%@", ele, ele, [ele class]);}}
}

输出结果:

1969-12-31 23:59:59 +0000是标签指针, realValue == 0xeadd27e440800000, 类名:__NSTaggedDate
1970-01-01 00:00:00 +0000是标签指针, realValue == 0xeadd27e440000000, 类名:__NSTaggedDate
1970-01-01 00:00:01 +0000是标签指针, realValue == 0xeadd27e43f800000, 类名:__NSTaggedDate
2001-01-01 00:00:00 +0000是标签指针, realValue == 0xe000000000000000, 类名:__NSTaggedDate
2001-01-01 00:00:02 +0000是标签指针, realValue == 0xe110000000000000, 类名:__NSTaggedDate
2020-03-18 07:21:03 +0000是标签指针, realValue == 0xe2d21101affe9e3d, 类名:__NSTaggedDate

可以看出:

  • tag index为0xe(0b1100最高位1表示标签指针,低三位为6表示NSDate类型);
  • 在2001-01-01 00:00:00 这个特殊的时间点好像是一个坐标系原点,所有的时间都在这个时间点的基础上做相对偏移.

tagged pointer相关推荐

  1. 3-runtime 之 Tagged Pointer

    Tagged Pointer 是自从iPhone 5s 之后引入的特性 1 先说一下iOS的内存布局 代码区:存放编译之后的代码 数据段 :字符串常量 : NSString *hello = @&qu ...

  2. 内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池

    先上代码,我们平时用的定时器,cadisplaylink.nstimer,CADisplayLink.NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 ...

  3. iOS之深入解析内存管理Tagged Pointer的底层原理

    一.前言 ① Tagged Pointer 概念 iOS 开发者对"引用计数"这个名词肯定不陌生,引用计数是苹果为了方便开发者管理内存而引入的一个概念.当引用计数为 0 时,对象就 ...

  4. Tagged Pointer分析

    在objc4源码中,我们经常会在函数中看到Tagged Pointer.Tagged Pointer究竟是何方神圣? 从64位系统开始,iOS引入了Tagged Pointer技术,用于优化小对象(N ...

  5. Tagged Pointer 含义的解释

    一.含义 iOS 是在 64bit 开始,引入的 Tagged Pointer 技术 用于优化 NSNumber.NSDate.NSString 等 小对象 的存储 比如: 小型的 OC 对象,就可能 ...

  6. 内存管理之Tagged pointer

    iOS开发者对引用计数这个名词肯定不陌生,引用计数是苹果为了方便开发者管理内存而引入的一个概念,当引用计数为0时,对象就会被释放.但是,真的是所有对象都是这样吗? 内存分配 iOS将虚拟内存按照地址由 ...

  7. Tagged Pointer遐想

    Tagged Pointer遐想 一.NSString __NSCFConstantString NSTaggedPointerString __NSCFString copy mutableCopy ...

  8. macOS 与 iOS 中的 Tagged Pointer

    目录 Tagged Pointer 的简介 解除 Tagged Pointer 的数据混淆 Tagged Pointer 的原理:macOS Tagged Pointer 的原理: iOS Tagge ...

  9. 简介 Tagged Pointer (标记指针)

    在计算机科学中,Tagged Pointer 是一个指针(内存地址),它具有与其关联的附加数据,例如,indirection bit 或 引用计数. 这些附加数据通常是"折叠"在指 ...

最新文章

  1. java基础国庆作业_java程序设计国庆作业
  2. 31 多线程同步之Lock(互斥锁)
  3. Argus(ZOJ Problem Set - 2212)(优先队列)
  4. Think as developer, 从深入理解业务实现框架开始
  5. C++学习笔记--(1)
  6. React 和 Vue的特点
  7. ASP.NETmvc常用JQUERY收藏【jquery.form.js结合jquery.validate.js】
  8. css-四种css导入方式
  9. 【树】判断给定森林中有多少棵树(简单做法)
  10. 使用tensorflow实现机器学习中的线性拟合
  11. 关于libusb-win32开发的经验
  12. 备忘: MIRACL 大数运算库使用手册
  13. js获取日期选择器值html,利用Query+bootstrap和js两种方式实现日期选择器
  14. 计算机光驱启动设置,bios设置光驱启动图文教程
  15. 【未完成】7-7 新浪微博热门话题 (30 分)
  16. 劈开迷雾:蘑菇街搜索架构及搜索排序实践
  17. 编译和执行区别 c语言,C语言编译和执行分析
  18. 奥鹏 大工21秋《计算机网络技术》在线作业
  19. v-loam源码阅读(一)视觉特征
  20. Windows文件资源管理器,搜索框的使用技巧

热门文章

  1. 常用电商系统优劣势对比—管易云、远丰电商、电商宝、海商、旺店通ERP、百数
  2. C罗8000W英镑到底是多少钱?!
  3. 【连载】从单片机到操作系统⑥——FreeRTOS任务切换机制详解
  4. 电子调谐器的主要引脚及作用
  5. 云原生安全,这也是一道必答题
  6. Java数据结构与算法_线性表_顺序表与链表
  7. 微信小程序之微信登入
  8. fabric2.0安装时github-production-release-asset-2e65be.s3.amazonaws.com连接失败
  9. 服务器里怎么给网站刷排名,细数刷排名软件的原理与实现过程
  10. 国家税务总局全国增值税发票查验平台验证码刷不出来显示系统繁忙的解决方法