Tagged Pointer
内存布局
- 代码段:编译之后的代码
- 数据段
- 字符串常量:比如NSString *str = @“123”
- 已初始化数据:已初始化的全局变量、静态变量等
- 未初始化数据:未初始化的全局变量、静态变量等
- 堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大-
- 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
Tagged Pointer
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer
,比如NSNumber的intValue方法
,直接从指针提取数据,节省了以前的调用开销
在小对象的时候, 使用Tagged Pointer,NSNumber指针里面存储的数据变成了:Tag + Data
,也就是将数据直接存储在了指针中,不在需要存取来获取值了。
我们分别打印abcdefghijk和abc字符串的类型。
NSString *str1 = [NSString stringWithFormat:@“abc”];
NSString *str2 = [NSString stringWithFormat:@“abcdefghijk”];
NSLog(@“\n[str1 class]=%@\n[str2 class]=%@”,[str1 class],[str2 class]);
打印结果:
根据打印发现str1是NSTaggedPointerString类型,是不通过set方法找对象的。
我们也可以在源码中找到相关实现,
-
- 在NSObject.mm中查找retain方法的实现
- (id)retain {
return ((id)self)->rootRetain();
}
-
- 点击进入rootRetain方法,我们可以在里面找到:
if (isTaggedPointer())
return (id)this;
也就是说如果是TaggedPointer类型,直接返回,不需要根据指针查找。
- 点击进入rootRetain方法,我们可以在里面找到:
判断是否是TaggedPointer
我们点击isTaggedPointer方法
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
- define _OBJC_TAG_MASK 1UL
#if TARGET_OS_OSX && __x86_64__
# define OBJC_MSB_TAGGED_POINTERS 0
#else
# 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
在判断是否是TaggedPointer的时候,在iOS平台和MAC平台还是不太一样的
1、iOS平台,需要把1向左移动63位,也就是最高有效位是1(第64bit)
2、在Mac平台,最低有效位是1
面试题
-
什么是Tagged Pointer?
答:Tagged Pointer是一种优化技术,用于存储小对象(如NSNumber、NSDate和NSString)的值。与普通对象不同,Tagged Pointer直接将数据存储在指针本身,而不是存储在堆上的内存块。这种技术利用了64位指针的高位来存储类型信息和实际数据,从而避免了分配和释放内存的开销,提高了性能和效率。 -
Tagged Pointer的工作原理是什么?
答:
Tagged Pointer的工作原理基于将指针的部分位 用作标记(tag)和数据存储。具体实现如下:- 标记位(Tag Bits):在64位指针的高位部分,用若干位来存储标记信息。这些标记位用来区分不同类型的Tagged Pointer(如NSNumber、NSDate等)。
- 数据位(Data Bits):在剩余的位中直接存储实际数据。例如,对于NSNumber,可以直接存储整数值或浮点数值。
- 类型检测:在需要时,通过检测指针的高位标记,判断该指针是否是Tagged Pointer以及它的类型。
-
如果检测一个指针是否是Tagged Pointer?
答:
检测一个指针是否是Tagged Pointer,可以通过检查指针的高位标记。具体实现方式因不同版本的Objective-C运行时而异,但常见的方式是检查指针是否设置了某些特定位。例如,在64位系统上,如果指针的最高位为1,则该指针可能是Tagged Pointer。代码示例如下:
BOOL isTaggedPointer(id pointer) {
return ((uintptr_t)pointer & _OBJC_TAG_MASK) != 0;
}
其中,_OBJC_TAG_MASK是用于检测Tagged Pointer的掩码。
- 给出一个Tagged Pointer NSNumber的示例,并解释其内部结构
假设我们有一个Tagged Pointer类型的NSNumber对象,其内部结构可能如下:
- 标记位:在指针的高位,用于标记该指针是Tagged Pointer以及它的类型(例如,NSNumber)。
- 数据位:在指针的低位,存储实际的数值数据。
例如,假设一个64位指针的结构如下:
[63 ... 60] [59 ... 4] [3 ... 0]
标记位 数据位 类型标记
假设标记位为0b1110(表示NSNumber),类型标记为0b0001(表示整数类型),数据位为0b0000000000000000000000000000000000000000000000000000000000001101(表示整数13)。则这个Tagged Pointer的值表示一个NSNumber对象,其数值为13。
怎么理解这个?
这个比特位分布,和之前介绍的 isa_t 结构有些不同。这个新的比特位分布是 Objective-C 运行时系统中另一种优化技术 - “Packed Tagged Pointer”。
下面我来解释一下这个结构的每个部分:
[63 … 60] 标记位 (4 bits):
这 4 个比特位用于标识这是一个 Packed Tagged Pointer,而不是普通的 isa 指针。
它们会被设置为一个特殊的标记值,用于在运行时快速识别这种指针。
[59 … 4] 数据位 (56 bits):
这 56 个比特位用于存储对象的数值信息和类信息。
具体的编码方式取决于对象的类型。
[3 … 0] 类型标记 (4 bits):
这 4 个比特位用于标识对象的具体类型,比如整数、浮点数、字符串等。
运行时系统会根据这个类型标记来解析存储在数据位中的实际值。
与之前介绍的 Tagged Pointer 相比,这种 Packed Tagged Pointer 的优势是能够存储更多的数据信息,提高了存储密度。