• iOS中 Tagged Pointer 技术


    前言:

    ​ 从64位开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储。

    Tagged Pointer主要为了解决两个问题:

    1. 内存资源浪费,堆区需要额外的开辟空间
    2. 访问效率,每次set/get都需要访问堆区,浪费时间, 而且需要管理堆区对象的声明周期,降低效率

    Tagged Pointer特点:

    1. 专门用来存储小对象,比如NSString,NSNumber,NSDate
    2. Tagged Pointer指针的值不再是堆区地址,而是包含真正的值。所以它不会在堆上再开辟空间了,也不需要管理对象的生命周期了。
    3. 内存读取提升3倍,创建比之前快100多倍,销毁速度更快

    一、引入Tagged Pointer 前后对比

    在这里插入图片描述

    1、引入前

    NSNumber等对象需要动态分配内存、维护引用计数等。 总共的空间= 指针空间 + 堆中分配的空间

    2、引入后

    NSNumber等对象,只需要分配一个指针即可,这个指针内部会包含这些数据内容。
    总空间 = 指针空间
    因为不用去用对象的方式管理引用计数,所以省却了 retainrelease操作。

    二、Tagged Pointer 原理

    number1只有栈上的指针内存;而maxNum不仅有指针内存,在堆中还分配了32字节的内存用于存储该变量的值。通过观察发现,对象的number1number2number3number4都存储在了对应的指针中;而maxNum不同由于数据过大,导致无法 1 个指针 8 个字节的内存根本存不下,而申请了32字节堆内存。

    1. NSString类型的Tagged Pointer指针与基本类型的指针是不一样的,末尾的数字为字符串的长度;
    2. NSString类型的Tagged Pointer指针存储char类型,返回的是ASCII码(该值为16进制的,需要进行十进制转换)

    三、如何判断是否使用了 Tagged Pointer 技术

    BOOL isTaggedPointer(id pointer) {
        return (long)(__bridge void *)pointer & 1;
    }
    

    该函数就是调用了isTaggedPointer

    四、使用 Tagged Pointer 注意点

    ​ 我们知道,所有OC对象都有isa指针,而Tagged Pointer并不是真正的对象,它没有isa指针,所以如果你直接访问Tagged Pointerisa成员的话,在编译时将会有警告。

    五、面试题

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

    两者运行结果有何不同?

    首先看self.name = [NSString stringWithFormat:@"abcdefghijk"];

    在这里插入图片描述

    崩溃,并且崩溃在objc_release的地方。

    是什么原因导致崩溃的呢?

    我们知道,
    self.name = [NSString stringWithFormat:@"abcdefghijk"];
    其实是调用了
    [self setName:[NSString stringWithFormat:@"abcdefghijk"]];

    而setName:的实现是:

    - (void)setName:(NSString *)name
    {
        if (_name != name) {
            [_name release];//老的释放掉
            _name = [name copy];//传入的值copy后赋值给_name
        }
    }
    

    由于是async异步操作,self.name = [NSString stringWithFormat:@"abcdefghijk"];即[_name release];有可能会被多条线程同时操作。导致,线程n把_name释放掉,线程n+1又要执行_name的释放,从而造成_name已经被释放两次,第二次访问的时候,_name已经释放过,造成坏内存访问。

    解决方法一:atomic

    @property (copy, atomic) NSString *name;
    从而:

    - (void)setName:(NSString *)name
    {
        //加锁操作
        if (_name != name) {
            [_name release];
            _name = [name copy];
        }
        //解锁操作
    }
    

    解决方法二:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i<1000; i++) {
            dispatch_async(queue, ^{
            //加锁
                self.name = [NSString stringWithFormat:@"abcdefghijk"];
            });
            //解锁
        }
    

    self.name = [NSString stringWithFormat:@"abc"];
    为何没有崩溃呢?

    在这里插入图片描述

    在这里插入图片描述

    从类型可以看出来,
    内容多的name类型是__NSCFString
    内容少的name类型是NSTaggedPointerString

    在这里插入图片描述

    这就是原因所在。

    内容少的name,由于类型是NSTaggedPointerString,在赋值的时候
    是直接在指针里面取值,而不需要release操作,因此,不会崩溃

  • 相关阅读:
    JVM年轻代(young generation)老年代(old generation tenured)持久代(permanent generation)GC
    php+mysql计算机公共课在线学习网站
    Sysweld笔记:利用稳态算法加速算法模拟焊接过程的残余应力
    记一次 Visual Studio 2022 卡死分析
    七牛云图床设置
    遥感数据与作物模型同化
    SqlServer如何将mdf、ldf文件导入数据库—两种解决方法
    包载信使mRNA的多西环素纳米脂质体|雷公藤红素纳米脂质体RNA核糖核酸(实验原理)
    牛客刷题<25>输入序列连续的序列检测
    洛谷_P1007 独木桥_思维
  • 原文地址:https://www.cnblogs.com/r360/p/16524649.html