【iOS】Tagged Pointer

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方法找对象的。

我们也可以在源码中找到相关实现,

    1. 在NSObject.mm中查找retain方法的实现
objc 复制代码
- (id)retain {
	return ((id)self)->rootRetain();
}
    1. 点击进入rootRetain方法,我们可以在里面找到:
      if (isTaggedPointer())
      return (id)this;
      也就是说如果是TaggedPointer类型,直接返回,不需要根据指针查找。

判断是否是TaggedPointer

我们点击isTaggedPointer方法

_objc_isTaggedPointer(const void * _Nullable ptr)

{

return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;

}

  • define _OBJC_TAG_MASK 1UL
objc 复制代码
#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

面试题

  1. 什么是Tagged Pointer?

    答:Tagged Pointer是一种优化技术,用于存储小对象(如NSNumber、NSDate和NSString)的值。与普通对象不同,Tagged Pointer直接将数据存储在指针本身,而不是存储在堆上的内存块。这种技术利用了64位指针的高位来存储类型信息和实际数据,从而避免了分配和释放内存的开销,提高了性能和效率。

  2. Tagged Pointer的工作原理是什么?

    答:

    Tagged Pointer的工作原理基于将指针的部分位 用作标记(tag)和数据存储。具体实现如下:

    1. 标记位(Tag Bits):在64位指针的高位部分,用若干位来存储标记信息。这些标记位用来区分不同类型的Tagged Pointer(如NSNumber、NSDate等)。
    2. 数据位(Data Bits):在剩余的位中直接存储实际数据。例如,对于NSNumber,可以直接存储整数值或浮点数值。
    3. 类型检测:在需要时,通过检测指针的高位标记,判断该指针是否是Tagged Pointer以及它的类型。
  3. 如果检测一个指针是否是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的掩码。

  1. 给出一个Tagged Pointer NSNumber的示例,并解释其内部结构

假设我们有一个Tagged Pointer类型的NSNumber对象,其内部结构可能如下:

  • 标记位:在指针的高位,用于标记该指针是Tagged Pointer以及它的类型(例如,NSNumber)。
  • 数据位:在指针的低位,存储实际的数值数据。

例如,假设一个64位指针的结构如下:

css 复制代码
[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 的优势是能够存储更多的数据信息,提高了存储密度。

相关推荐
SoraLuna8 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
追风林8 小时前
mac 本地docker-mysql主从复制部署
mysql·macos·docker
yqcoder8 小时前
mac 安装 nodemon
macos
一ge科研小菜鸡8 小时前
macOS开发环境配置与应用开发(详细讲解)
macos
hairenjing11238 小时前
使用 Mac 数据恢复从 iPhoto 图库中恢复照片
windows·stm32·嵌入式硬件·macos·word
2401_8658548811 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
zorchp14 小时前
在 MacOS 上跑 kaldi
macos·kaldi
德育处主任15 小时前
Mac和安卓手机互传文件(ADB)
android·macos
土小帽软件测试16 小时前
jmeter基础01-2_环境准备-Mac系统安装jdk
java·测试工具·jmeter·macos·软件测试学习
小沈同学呀18 小时前
Mac M1 Docker创建Rocketmq集群并接入Springboot项目
macos·docker·java-rocketmq·springboot