美文网首页iOS底层原理
iOS之Tagged Pointer简单总结

iOS之Tagged Pointer简单总结

作者: 好_好先生 | 来源:发表于2019-10-25 16:03 被阅读0次

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

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

    相关文章

      网友评论

        本文标题:iOS之Tagged Pointer简单总结

        本文链接:https://www.haomeiwen.com/subject/zxosvctx.html