zoukankan      html  css  js  c++  java
  • Autorelease返回值的快速释放机制

    + (instancetype)createSark {
        return [self new];
    }
    // caller
    Sark *sark = [Sark createSark];
    

    编译器改写成了形如下面的代码:

    + (instancetype)createSark {
        id tmp = [self new];
        return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
    }
    // caller
    id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我们调用retain
    Sark *sark = tmp;
    objc_storeStrong(&sark, nil); // 相当于代替我们调用了release
    
    

    runtime使用了一些黑魔法进行了优化

    Thread Local Storage

    Thread Local Storage(TLS)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写,比如在非arm架构下,使用pthread提供的方法实现:

    void* pthread_getspecific(pthread_key_t);
    int pthread_setspecific(pthread_key_t , const void *);
    

    在返回值身上调用objc_autoreleaseReturnValue方法时,runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里,发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。
    于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。

    于是问题又来了,
    只能动用更高级的黑魔法。

    __builtin_return_address

    假如被调方和主调方只有一边是ARC环境编译,(比如我们在ARC环境下用了非ARC编译的第三方库,或者反之),需要用到__builtin_return_address

    这个内建函数原型是 char *__builtin_return_address(int level),
    作用是得到函数的返回地址,参数表示层数,如__builtin_return_address(0)表示当前函数体返回地址,传1是调用这个函数的外层函数的返回值地址,以此类推。

    - (int)foo {
        NSLog(@"%p", __builtin_return_address(0)); // 根据这个地址能找到下面ret的地址
        return 1;
    }
    // caller
    int ret = [sark foo];
    
    • 函数的返回值地址,也就对应着调用者结束这次调用的地址(或者相差某个固定的偏移量,根据编译器决定)
    • 如果一个函数返回前知道调用方是ARC还是非ARC,就有机会对于不同情况做不同的处理 ##黑魔法之反查汇编指令

    通过上面的__builtin_return_address加某些偏移量,被调方可以定位到主调方在返回值后面的汇编指令:

    于是乎,就有了下面的这个函数,入参是调用方__builtin_return_address传入值

    static bool callerAcceptsFastAutorelease(const void * const ra0) {
        const uint8_t *ra1 = (const uint8_t *)ra0;
        const uint16_t *ra2;
        const uint32_t *ra4 = (const uint32_t *)ra1;
        const void **sym;
        // 48 89 c7    movq  %rax,%rdi
        // e8          callq symbol
        if (*ra4 != 0xe8c78948) {
            return false;
        }
        ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l;
        ra2 = (const uint16_t *)ra1;
        // ff 25       jmpq *symbol@DYLDMAGIC(%rip)
        if (*ra2 != 0x25ff) {
            return false;
        }
        ra1 += 6l + (long)*(const int32_t *)(ra1 + 2);
        sym = (const void **)ra1;
        if (*sym != objc_retainAutoreleasedReturnValue)
        {
            return false;
        }
        return true;
    }
    

    它检验了主调方在返回值之后是否紧接着调用了objc_retainAutoreleasedReturnValue,如果是,就知道了外部是ARC环境,反之就走没被优化的老逻辑。

    其他Autorelease相关知识点

    使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        // 这里被一个局部@autoreleasepool包围着
    }];
    

    当然,在普通for循环和for in循环中没有,所以,还是新版的block版本枚举器更加方便。for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool咯。

  • 相关阅读:
    Spring IOC(二)beanName 别名管理
    Spring IOC(六)依赖查找
    Spring IOC(五)依赖注入
    Spring IOC(七)类型推断
    Spring 循环引用(二)源码分析
    Spring 循环引用(一)一个循环依赖引发的 BUG
    Spring IOC(四)FactoryBean
    Spring 中的类加载机制
    Spring IOC(三)单例 bean 的注册管理
    Spring Environment(三)生命周期
  • 原文地址:https://www.cnblogs.com/rainySue/p/Autorelease-fan-hui-zhi-de-kuai-su-shi-fang-ji-zhi.html
Copyright © 2011-2022 走看看