前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >iOS_Tagged Pointer是什么,结构

iOS_Tagged Pointer是什么,结构

作者头像
mikimo
发布于 2022-07-20 06:40:21
发布于 2022-07-20 06:40:21
48500
代码可运行
举报
文章被收录于专栏:iOS开发~iOS开发~
运行总次数:0
代码可运行

文章目录

iOS Tagged Pointer

问题

如果要存一个NSNumber对象,其值是一个整数。

32位CPU下:指针4位 -> 值4位 (一共需要8位)

64位CPU下:指针8位 -> 值8位 (一共需要16位)(未使用Tagged Pointer情况下)

这样的数据从 32 位机器迁移到 64 位机器中后,占用的内存会翻倍。为了节省内存和提高执行效率,苹果提出了Tagged Pointer指针(标记指针)。

原理

将指针(8字节)拆成两部分:一部分直接保存数据,另一部分作为标记(这是一个特别的指针,不指向任何一个地址)

(拿一个整数来说,4个字节所能表示的有符号整数就可达20 多亿,注:2^31=2147483648,另外 1 位作为符号位)

结构

NSNumber

NSString

  • Tagged Pointer:1表示Tagged Pointer、0表示非Tagged Pointer
  • 类标志位
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  // objc-internal.h
  enum {
    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_TAG_RESERVED_7        = 7, 
  	......
  };

特点

  • 专门用来存储小的对象,如:NSStringNSNumberNSData
  • 指针值不再是地址,而是真正的值。(所以,实际上它不再是一个对象了,而是个普通变量而已。因此,它的内存并不存储在堆中,也不需要mallocfree
  • 在内存读取上有着3倍的效率,创建时比以前快106倍

当8个字节可以承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,如果8字节承载不了时,则又用以前的方式来生成普通的指针。

测试

测试准备:

在现在的版本中,为了保证数据安全,苹果对 Tagged Pointer 做了数据混淆,开发者通过打印指针无法判断它是不是一个Tagged Pointer,更无法读取Tagged Pointer的存储数据。

所以在分析Tagged Pointer之前,我们需要先关闭Tagged Pointer的数据混淆,以方便我们调试程序。通过设置环境变量OBJC_DISABLE_TAG_OBFUSCATIONYES来关闭。

(设置步骤:Edit Scheme -> Run -> Arguments -> Environment Variables -> 添加key:OBJC_DISABLE_TAG_OBFUSCATION,value:YES)

NSNumber

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NSNumber *num0 = @1;
NSNumber *num1 = @(0xffffffffffffff); // 14个f
// 一共15位(1位4个bit),最高位Tag+类标志,最低位数据类型,所以当大于13个f时就表示不了,需要创建对象了)
NSLog(@"%p", num0); // 0xb000000000000012 (Tagged Pointer 标记指针)
NSLog(@"%p", num1); // 0x6000006965a0 (正常指针)

num0的指针:0xb0000000000000120x表示十六进制)

  • 最高位 (该例是b,转换为二进制是1011)

最高bit位:Tagged Pointer(该例是1,表示是Tagged Pointer

倒数1-3个bit位:类标志位 (该例是:011转为十进制是3,对应OBJC_TAG_NSNumber

  • 最低位:数据类型(该例是2,转换为二进制是0010,也就是2,对应int
  • 剩下中间的位:存储数据(该例是00000000000001,对应num0的值1)

NSString

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NSString *str1 = [NSString stringWithFormat:@"0"];
NSString *str2 = [NSString stringWithFormat:@"abcdefghij"]; // 存在堆区 (超过9个字符)
NSLog(@"%p %@", str1, [str1 class]);
NSLog(@"%p %@", str2, [str2 class]);
// 0xa000000000000301 NSTaggedPointerString (值直接存储在指针上)
// 0x600003d3c620 __NSCFString (存在堆区)

str1的指针:0xa0000000000003010x表示十六进制)

  • 最高位 (该例是a,转换为二进制是1010)

最高bit位:Tagged Pointer(该例是1,表示是Tagged Pointer

倒数1-3个bit位:类标志位 (该例是010,转换十进制是2,对应OBJC_TAG_NSString

  • 最低位:字符长度(该例是1,转换为二进制是0001,十进制也是1,表示字符串长度1)
  • 剩下中间的位:存储数据(该例是00000000000030,转为十进制是48,对应ASCII码表中的0)

注意事项

isa指针

因为Tagged Pointer实现的对象,并不是真正的对象,它没有isa指针,如果直接访问其isa成员,就会报错

面试题

题1:执行以下两段代码,有什么区别?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
  dispatch_async(queue, ^{
    self.name = [NSString stringWithFormat:@"abcdefghi"]; // NSTaggedPointerString (运行正常)
  });
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
  dispatch_async(queue, ^{
    self.name = [NSString stringWithFormat:@"abcdefghij"]; // __NSCFString (Crash)
  });
}

当字符串设置为@"abcdefghij"时会crash,如下:

原因:赋值时会调用setter方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (void)setName:(NSString *)name {
  if (_name != name) {
    [_name release]; // 异步并发执行setter方法,release就有可能连续执行,造成过度释放
    _name = [name copy];
  }
}

因为多个赋值是异步的,而且放在了并行队列里。就会创建多个线程同步处理多个赋值操作。release就有可能连续执行,造成过度释放。

而当字符少于10个时,系统采用了Tagged Pointer机制将数据直接存储在指针上。 objc_release 内部会判断,如果是Tagged Pointer则不会进行release,直接赋值。所以不会导致过度释放的BAD_ACCESS错误。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
__attribute__((aligned(16), flatten, noinline))
void objc_release(id obj) {
   if (!obj) return;
   if (obj->isTaggedPointer()) return; // 如果是TaggedPointer则不会进行release
   return obj->release();
}

解决方案:

  • 方法1:使用串行队列
  • 方法2:使用atomic
  • 方法3:赋值方法前后:加锁、解锁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 加锁
self.name = [NSString stringWithFormat:@"abcdefghij"];
// 解锁

参考:

深入理解Tagged Pointer

iOS - 老生常谈内存管理(五):Tagged Pointer(Mac OS + iOS 下 NSNumber + NSString 的Tagged Pointer 结构图)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • iOS Tagged Pointer
    • 问题
    • 原理
    • 结构
      • NSNumber
      • NSString
    • 特点
    • 测试
      • 测试准备:
      • NSNumber
      • NSString
    • 注意事项
      • isa指针
    • 面试题
      • 题1:执行以下两段代码,有什么区别?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档