iOS内存管理—Tagged Pointer

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

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

一、内存布局

iOS内存管理—Tagged Pointer_第1张图片
内存布局.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 Pointer_第2张图片
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);
        });
    }

这段代码运行会发生问题,但是如果把代码稍作改动:self.nameStr = [NSString stringWithFormat:@"tagged"];,一切都正常了。
为什么?因为两个对象不一样,一个是NSTaggedPointerString,一个是NSCFString。

2.4 总结

  • Tagged Pointer 并非真的对象,没有isa指针,只是看起来和对象一致。
  • Tagged Pointer 存储在栈中,其存取值,均已tag+data形式。
  • Tagged Pointer 不支持retain、release、autorelease、malloc、free等关于对象的内存管理。

你可能感兴趣的:(iOS内存管理—Tagged Pointer)