iOS内存管理—Tagged Pointer

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

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

一、内存布局

内存布局.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十六进制结果转换为二进制:


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等关于对象的内存管理。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349