iOS内存管理—Tagged|iOS内存管理—Tagged Pointer

在介绍Tagged Pointer之前,先简单介绍一下ios的内存布局。

这里大部分内容是从其他地方整理搬运而来,加上部分自己的理解,站在巨人的肩膀上,让自己能看的更远。
一、内存布局

iOS内存管理—Tagged|iOS内存管理—Tagged Pointer
文章图片
内存布局.png
整个内存由高到低主要分为五大块:
内核区:手机内存总共4GB,我们只用到了3GB,剩余1GB给内核区,部分给保留字段。
栈区:函数,方法 ,是由系统编译器自动管理,不需要程序员手动管理
堆区:通过alloc、malloc、block copy等生成对象所分配的内存空间,释放工作由程序员手动管理
BSS段:未初始化的全局变量,静态变量
数据段:初始化的全局变量,静态变量
代码段:编译之后的代码
保留区
0xc0000000用计算机兑换为10进制,计算结果为3GB。
不绝对准确的内存首地址:0x6在堆区,0x7在栈区,0x1数据段、BSS内存地址。
问题1:堆区为什么比栈区慢?堆区,先从栈区找到地址,通过地址找到变量(po objc),最后找到变量指向的堆区空间(po &objc);栈区,直接通过cpu的寄存器查找。
注意点:
  1. static修饰的静态全局变量只针对文件有效,与类、分类都没有关系。
  2. extern,用于跨文件访问。
二、Tagged Pointer
早在在2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升。
一般8~10位的小对象,苹果会自动将其转换为Tagged Pointer类型。
2.1 源码分析 他的初始化在main方法之前,通过_read_images函数调用initializeTaggedPointerObfuscator实现,在10.14后又做了处理,不在直接展示值:
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_TAG_MASK; } }

TaggedPointer编码和解码函数:
static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; }static inline bool _objc_taggedPointersEnabled(void) { extern uintptr_t objc_debug_taggedpointer_mask; return (objc_debug_taggedpointer_mask != 0); }

decod和dencode都是异或^同一个变量,为什么呢?
a = a ^ b; b = a ^ b; a = a ^ b;

2.2 指针 + 值 如果打印Tagged Pointer的指针地址,你会发现是一个奇怪的数字和常规的地址表示不一样。
因为实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。Tagged Pointer = 指针 +
所以,它的内存并不存储在堆中,也不需要malloc和free,以及引用计数的处理。
这在retainrelease方法中有很直观的体现。
objc_retain(id obj) { if (!obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->retain(); }

前面提到在10.14后苹果又做了处理,不在直接展示值,如果需要直接展示需要做相应的解码处理:
//引入 extern uintptr_t objc_debug_taggedpointer_obfuscator; uintptr_t _objc_decodeTaggedPointer_(id ptr){ return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; }- (void)viewDidLoad { [super viewDidLoad]; NSString *str1 = [NSString stringWithFormat:@"a"]; NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str1)); NSNumber *number1 = [NSNumber numberWithInt:1]; //@1; NSLog(@"%@ - %p - %@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1)); }

打印结果:
[55584:651418] 0xa000000000000611 [55584:651418] __NSCFNumber - 0xf2c99228254ad581 - 1 - 0xb000000000000012

2.3 存储格式 NSNumber & NSString
- (void)testNumber{NSNumber *charNumber = [NSNumber numberWithChar:'1']; NSNumber *shortNumber = [NSNumber numberWithShort:1]; NSNumber *intNumber = [NSNumber numberWithInt:1]; NSNumber *longNumber = [NSNumber numberWithLong:1]; NSNumber *floatNumber = [NSNumber numberWithFloat:1.0]; NSNumber *doubleNumber = [NSNumber numberWithDouble:1.0]; NSLog(@"%p - 0x%lx", charNumber, _objc_decodeTaggedPointer_(charNumber)); NSLog(@"%p - 0x%lx", shortNumber, _objc_decodeTaggedPointer_(shortNumber)); NSLog(@"%p - 0x%lx", intNumber, _objc_decodeTaggedPointer_(intNumber)); NSLog(@"%p - 0x%lx", longNumber, _objc_decodeTaggedPointer_(longNumber)); NSLog(@"%p - 0x%lx", floatNumber, _objc_decodeTaggedPointer_(floatNumber)); NSLog(@"%p - 0x%lx", doubleNumber, _objc_decodeTaggedPointer_(doubleNumber)); }

打印结果:
[64055:782611] 0xc0566246b12ba50d - 0xb000000000000310 [64055:782611] 0xc0566246b12ba60c - 0xb000000000000011 [64055:782611] 0xc0566246b12ba60f - 0xb000000000000012 [64055:782611] 0xc0566246b12ba60e - 0xb000000000000013 [64055:782611] 0xc0566246b12ba609 - 0xb000000000000014 [64055:782611] 0xc0566246b12ba608 - 0xb000000000000015

将charNumber十六进制结果转换为二进制:

iOS内存管理—Tagged|iOS内存管理—Tagged Pointer
文章图片
Tagged Pointer.png 第64位:最高位, 说明这个指针是一个Tagged Pointer
第61-63位:是11(十进制是3),也就是OBJC_TAG_NSNumber(查上面的枚举)
中间56位:就是真正的值了,0011 0001对应ASCII的1
第1-4位:NSNumber的类型:char是0、short是1、int是2、float是4
OBJC_MSB_TAGGED_POINTERS:64-bit的mac,tag存储在LSB(Least Significant Bit 最低位)。其它情况比如64位的真机和模拟器,tag存储在MSB(Most Significant Bit 最高位)。
NSString与NSNumber类似:
第64位:最高位,说明这个指针是一个Tagged Pointer
第61-63位:10(十进制是2),也就是OBJC_TAG_NSString
中间56位:就是真正的值了
第1-4位:字符串长度
关于具体的计算方式,我没有找到相关的苹果开源文件,但是从_objc_makeTaggedPointer方法可以简单了解一下,这个方法做了一系列位运算,但是并不是最终数据。
2.4 面试题 代码如下:
for (int i = 0; i<100000; i++) { dispatch_async(dispatch_queue_create(0, 0), ^{ self.nameStr = [NSString stringWithFormat:@"学习taggedpointer,我们来了!"]; NSLog(@"%@",self.nameStr); }); }

【iOS内存管理—Tagged|iOS内存管理—Tagged Pointer】这段代码运行会发生问题,但是如果把代码稍作改动:self.nameStr = [NSString stringWithFormat:@"tagged"]; ,一切都正常了。
为什么?因为两个对象不一样,一个是NSTaggedPointerString,一个是NSCFString。
2.4 总结
  • Tagged Pointer 并非真的对象,没有isa指针,只是看起来和对象一致。
  • Tagged Pointer 存储在栈中,其存取值,均已tag+data形式。
  • Tagged Pointer 不支持retain、release、autorelease、malloc、free等关于对象的内存管理。

    推荐阅读