【iOS】Tagged Pointer

【iOS】Tagged Pointer

文章目录

前言

在之前的学习中笔者在字符串章节简单了解过这个Tagged Pointer后面笔者就没在多了解这部分内容,今天决定比较系统的学习一下有关于这部分内容的知识.

认识Tagged Pointer

标记指针(Tagged Pointer)是一种优化技术,用于在不分配额外内存的情况下存储小的对象或数字值。在这种技术中,指针的最低有效位LSB)用于存储特殊标记,而不是指向分配的内存地址。

在传统上,OC对象都是通过指针引用的,指针指向一个存储在堆内存中的实例对象.然而小对象在64位环境下就明明只占用了很小的一个内存空间,但是却占用了很大内存空间,为了节约内存和提高性能,OC引入了标记指针.也就是Tagged Pointer

Tagged Pointer通过修改指针的最低有效位来存储伊谢尔比较简单的值,如整数,浮点数和布尔值.这样就可以提高我们程序的性能和内存利用率.

  • 优势:

Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate

Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。

在内存读取上有着3倍的效率,创建时比以前快106倍。

为了存储或者访问一个NSNumber对象.我们需要在堆上维他分配内存,另外还要维护它的一个引用计数,管理生命周期

为了改进上面提到内存浪费的问题和效率问题,苹果提出了Tagged Pointer对象,由于NSNumber这种类型的变量值需要占用的内存大小常常不需要8个字节,所以我们可以讲一个对象的指针拆分成两个部分,一部分直接保存数据,另一个部分作为特殊标记,表示这是一个特别的指针.

使用案例

objc 复制代码
NSMutableString* stirng = [@"ab" mutableCopy];
for (int i = 0; i <= 15; i++) {
     [stirng appendFormat:@"c"];
      NSLog(@"%@ %@ %ld", [stirng copy], [[stirng copy] class], stirng.length);
}

结果:

这里可以看出如果字符串长度在10个以内,字符串的类型就是我们的NSTaggedPointerString超过10个的时候才是__NSCFString

刚刚使用的时候,我们发现这里的string进行了一次不可便拷贝,我们如果把这次不可变拷贝去掉,我们在来看一下这个函数

objc 复制代码
NSMutableString* stirng = [@"ab" mutableCopy];
        for (int i = 0; i <= 15; i++) {
            [stirng appendFormat:@"c"];
            NSLog(@"%@ %@ %ld", stirng , [stirng class], stirng.length);
        }

输出结果

这就是因为Tagged Pointer主要用于优化小的而且不可变的对象.

如果我们按照这种方式创建字符串的话

objc 复制代码
NSString *str = @"abcde";
NSLog(@"%@ %p", [str class], str);

__NSCFConstantString 0x100004318

Type: Notice | Timestamp: 2025-05-06 22:08:21.832109+08:00 | Process: GGDebug | Library: GGDebug | TID: 0x3b78fa

这是因为__NSCFConstantString是编译时确定的字符串,它们的值和内存地址在程序运行期间是不会改变的。而str在编译时就已经确定了,它的值和内存地址在程序运行期间是不会改变的,所以其类型是__NSCFConstantString,而不是NSTaggedPointerString。

结构

结构大致如下:

objc 复制代码
| 1bit | 3~4bits |     payload (60bits)      |
|  T   |  tag    |         data              |

Tagged Pointer 标记:这是用来标记该对象是否为Tagged Pointer对象的标志位。在macOS(x86)中,这个标识位是最后一位;在iOS(arm64)中,这是最高位。1表示是Tagged Pointer对象,0表示是普通对象。

Tag:这是对象类型的标记。在(macOS)x86架构中,它占据3位;在(iOS)arm64架构中,它占据2位。其中,值为7表示有扩展信息。

Extended:这部分用于扩展更多类型。在x86架构中,它占据4位;在arm64架构中,它占据5位。

payload:这是有效负载,用于存储真正的数据(除了标记位、tag以及extended)。但是为了安全,苹果对其进行了编码。

isa指针

Tagged Pointer的引入也带来了问题,即Tagged Pointer因为并不是真正的对象,而是一个伪对象,所以所有对象都有 isa 指针,而Tagged Pointer其实是没有的,因为它不是真正的对象。'

  • 苹果将Tagged Pointer引入,给 64 位系统带来了内存的节省和运行效率的提高。
  • Tagged Pointer通过在其最后一个 bit 位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其 isa 变量。

在32位环境下,对于每一个对象的引用计数都保存在一个外部的表中,每个对象的引用计数都保存在外部的一个表中,它对于每一个对象的持有操作是这样实现的:

  • 获取全局的记录引用计数的hash表
  • 为了线程安全,给该hash表加锁
  • 查找到对应对象的一个引用计数值
  • 将该引用计数加1,写回hash表
  • 给这个表解锁

从上面的这五个步骤可以看出,为了保证引用计数的增减操作都要先锁定这个表,在新的64位的环境下,isa的指针也变成了64位,其中有31位采用了类似与Tagged Pointer的一个概念,其中19位用来保存对象的引用计数,这样对引用计数的操作只用修改这个指针就可以了.只有引用计数的大小超过19位的时候,才会把引用计数保存到外部表.这样就有新的步骤:

  • 检查 isa 指针上面的标记位,看引1用计数是否保存在 isa变量中,如果不是,则使用以前的步骤,否则执行第2步
  • 检查当前对象是否正在释放,如果是,则不做任何事情
  • 增加该对象的引用计数,但是并不马上写回到isa 变量中。
  • 检杳增加后的引用计数的值是否能够被 19 位表示,如果不是,则切换成以前的办法,否则执行第5步。
  • 进行一个原子的写操作,将isa 的值写回。

经典面试题

下面两段代码的一个运行结果会是什么?

objc 复制代码
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 1000; i ++) {
        dispatch_async(queue, ^{
           
            self.string = [NSString stringWithFormat:@"111123123123123"];
        });
    }


dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 1000; i ++) {
        dispatch_async(queue, ^{
           
            self.string = [NSString stringWithFormat:@"11112"];
        });
    }

这两个代码唯一的区别在于一个name属性的字符串大于10,.一个小于10.在运行的时候会发现第一段代码崩溃,第二段代码可以正常运行:

第一段代码会报错的是坏内存访问

原因:

我们知道所有的属性实际上是这样实现的

objc 复制代码
  - (void)setString:(NSString *)string{
	if (_string != string) {
    	[_string release];
   	 _string = [string copy];
	}
}

这里因为前面一致是我们的堆上创建的字符串,导致它的一直进入函数里面执行realse方法,这样可能会导致多个线程同时调用realse方法,但是在第二个线程调用的时候string以及被释放了,所有就会出现一个坏内存报错:

解决方法:

  • 用atomic修饰我们的string
objc 复制代码
@property (atomic, copy) NSString* string;
  • 在外部修改stirng赋值的时候加上互斥锁
objc 复制代码
- (void)setString:(NSString *)string {
    @synchronized (self) {
        _string = [string copy];
    }
}
相关推荐
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
黑科技iOS上架3 小时前
iOS隐私合规扫描工具
ios·审核
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
可乐鸡翅yeah_3 小时前
hls.js 缓冲区参数 maxBufferLength、maxBufferSize 通俗讲解与业务调参
开发语言·javascript·ios·音视频·safari·m3u8
supabc1234 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
牛奔4 小时前
Mac 精准安装指定 macOS 版本完整教程(升级 + 降级通用)
macos
海盗12344 小时前
AI 新闻日报 2026-10-05:三周重构 30 万行 / macOS 给智能体加权限锁 / 具身订单结构被摆上台面
人工智能·macos·重构·机器人·人工智能aigc