iOS之Tagged Pointer简单总结

请问以下代码执行结果是什么:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000; i ++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
        });
    }
    NSLog(@"end");

运行结果:崩溃(坏内存访问)


坏内存访问.png

原因分析:
因为setter方法中,对strong修饰的属性会有一个retain和release的操作。在并发多线程的赋值操作中,都是对_name指针进行的操作,可能在_name刚刚被release后进行赋值操作,这个时候_name指向的内存地址是已经被释放了,所以造成了坏内存访问崩溃

- (void)setName:(NSString *)name
{
    [name retain];
    [_name relase];
    _name = name;
}

验证分析:


堆栈信息.png

解决办法:

  1. 将并发执行的任务改为串行执行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
  1. 将异步执行改为同步执行
dispatch_sync(queue, ^{
            self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
        });
  1. 将属性改为atomic

属性原子性

  1. 加锁
NSLock *lock = [[NSLock alloc] init];
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000; i ++) {
        dispatch_async(queue, ^{
            [lock lock];
            self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
            [lock unlock];
        });
    }
    NSLog(@"end");

再请问以下代码执行结果是什么:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000; i ++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abc"];
        });
    }
    NSLog(@"end");

为什么不崩溃了?因为没有用到引用计数的内存管理方法,使用的是TaggedPointer

TaggedPointer

  • 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象存储
  • 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
  • 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中,Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
  • 在内存读取上有着3倍的效率,创建时比以前快106倍。不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
  • 这是一个特别的指针,不指向任何一个地址
  • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据

查看类型

NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
    NSString *str2 = [NSString stringWithFormat:@"abc"];
        
    NSLog(@"%@ %@", [str1 class], [str2 class]);

查看内存地址:

NSNumber *number1 = @1;
    NSNumber *number2 = @2;
    NSNumber *number3 = @3;
    NSLog(@"number1 pointer is %p", number1);
    NSLog(@"number2 pointer is %p", number2);
    NSLog(@"number3 pointer is %p", number3);
    
    /*
    2017-03-10 12:07:50.731726 TaggedPoint[1690:50438] number1 pointer is 0x127 
    2017-03-10 12:07:50.731992 TaggedPoint[1690:50438] number2 pointer is 0x227 
    2017-03-10 12:07:50.732011 TaggedPoint[1690:50438] number3 pointer is 0x327
    */

上面的代码的执行结果以前是直接可以查看到结果的(注释部分),但现在的地址有些特殊,在10_14之后苹果对TaggedPointer进行了混淆,文件objc-runtime-new.m里写到:

static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        DisableTaggedPointerObfuscation) {
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}

混淆原理:使用objc_debug_taggedpointer_obfuscator对真正的内存地址异或操作

objc_debug_taggedpointer_obfuscator这个值是一个随机值,获取这个值有两种方法
方法一:通过断点LLDB指令获取:

(lldb) p/x objc_debug_taggedpointer_obfuscator
(void *) $0 = 0x4fd2047445c09ecd

方法二:看runtime的源码知道objc_debug_taggedpointer_obfuscator是个全局变量,只要在我们用的地方申明一下即可

extern uintptr_t objc_debug_taggedpointer_obfuscator;

通过nslog打印就可以

NSLog(@"%lx",objc_debug_taggedpointer_obfuscator);

方便查看,写一个方法用来解开混淆:

extern uintptr_t objc_debug_taggedpointer_obfuscator;

uintptr_t _objc_decodeTaggedPointer_(id ptr) {
    NSString *p = [NSString stringWithFormat:@"%ld", ptr];
    return [p longLongValue] ^ objc_debug_taggedpointer_obfuscator;
}

真实地址:

NSNumber *number1 = @1;
    NSNumber *number2 = @2;
    NSNumber *number3 = @3;
    NSLog(@"number1 pointer is %p---真实地址:==0x%lx", number1,_objc_decodeTaggedPointer_(number1));
    NSLog(@"number2 pointer is %p---真实地址:==0x%lx", number2,_objc_decodeTaggedPointer_(number2));
    NSLog(@"number3 pointer is %p---真实地址:==0x%lx", number3,_objc_decodeTaggedPointer_(number3));
    NSString *str3 = [NSString stringWithFormat:@"a"];
    NSString *str4 = [NSString stringWithFormat:@"b"];
    NSLog(@"str3 pointer is %p---真实地址:==0x%lx", str3,_objc_decodeTaggedPointer_(str3));
    NSLog(@"str4 pointer is %p---真实地址:==0x%lx", str4,_objc_decodeTaggedPointer_(str4));

打印结果:

2019-10-16 15:29:23.965970+0800 taggedpointer[64119:3260975] number1 pointer is 0xd206e93cb39459f2---真实地址:==0xb000000000000012
2019-10-16 15:29:23.966067+0800 taggedpointer[64119:3260975] number2 pointer is 0xd206e93cb39459c2---真实地址:==0xb000000000000022
2019-10-16 15:29:23.966152+0800 taggedpointer[64119:3260975] number3 pointer is 0xd206e93cb39459d2---真实地址:==0xb000000000000032
2019-10-16 15:29:23.966245+0800 taggedpointer[64119:3260975] str3 pointer is 0xc206e93cb3945ff1---真实地址:==0xa000000000000611
2019-10-16 15:29:23.966328+0800 taggedpointer[64119:3260975] str4 pointer is 0xc206e93cb3945fc1---真实地址:==0xa000000000000621

内存地址对比:

    // 0x8df5fe7e8dbc4702
    // 0x3df5fe7e8dbc4710
    // 0xb000000000000012

由此可见简单数字直接存储,字母存储为ASCII码
ASCII码打印:

NSLog(@"%lx,%lx",'a','b');
ASSCII.png

验证大数据存储至堆空间:

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