天天看點

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 *[email protected](%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咯。

轉載于:https://www.cnblogs.com/rainySue/p/Autorelease-fan-hui-zhi-de-kuai-su-shi-fang-ji-zhi.html