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;
}

验证分析:


iOS之Tagged Pointer简单总结_第1张图片
堆栈信息.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');
iOS之Tagged Pointer简单总结_第2张图片
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"];
        }

你可能感兴趣的:(iOS之Tagged Pointer简单总结)