OC--Autorelease

不解之謎1,NSLog(@"2222=====%@",obj2); // crash 野指針

@implementation TestObj
- (instancetype)init
{
    self = [super init];
    if (self) {
        __unsafe_unretained NSObject *obj1 = [TestObj getObj];
        NSLog(@"1111=====%@",obj1); // 運(yùn)行OK
        __unsafe_unretained NSObject *obj2 = [TestObj getObj2];
        NSLog(@"2222=====%@",obj2); // crash 野指針
    }
    return self;
}

+ (id)getObj {
    return [NSObject new];
}
+ (id)getObj2 {
    return [NSObject new];
}

不解之謎2,ViewController的viewDidLoad里面,NSLog不一樣

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak NSObject *obj1 = [ViewController getObj];
    NSLog(@"=====%@",obj1); // nil
    __weak NSObject *obj2 = [ViewController getObj];
    NSLog(@"=====%@",obj2);// 輸出對象
}
+ (id)getObj {
    return [NSObject new];
}

Autorelease與Autoreleasepool

參考:
ARC環(huán)境下編譯器到底對autorelease對象做了怎樣的優(yōu)化
黑幕背后的Autorelease
自動釋放池的前世今生 ---- 深入解析 Autoreleasepool
Objective-C 小記(8)autorelease

autoreleasepool

1、自動釋放池是由 AutoreleasePoolPage 以雙向鏈表的方式實(shí)現(xiàn)的
2、當(dāng)對象調(diào)用 autorelease 方法時(shí),會將對象加入 AutoreleasePoolPage 的棧中
3、調(diào)用 AutoreleasePoolPage::pop 方法會向棧中的對象發(fā)送 release 消息
4、新建線程會第一個autorelease對象時(shí)候,新建AutoreleasePool,線程銷毀AutoreleasePool釋放對象且銷毀
5、每個AutoreleasePoolPage對象大小為4096, 對象本身信息占 56 個字節(jié), 所以 begin() 需要排除這 56 個字節(jié), 真正用于存儲 autorelease 對象地址的內(nèi)存量為 end() - begin(), 共有 4040 個字節(jié), 可存儲 505 個 autorelease 變量(一個對象8個字節(jié)).

AutoreleasePoolPage

class AutoreleasePoolPage
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}

對應(yīng)字段意義:
1、magic:這個變量的類型是 magic_t,是用來檢查 AutoreleasePoolPage 的內(nèi)存沒有被修改的,放在第一個也就是這個原因,防止前面地址有內(nèi)容溢過來。
2、next:類型是 id *,存放的是下一個被 autorelease 的對象指針存放的地址。
3、thread:對應(yīng)的線程,這說明了自動釋放池是對應(yīng)線程的。
4、parent 和 child:用來保存前一個 AutoreleasePoolPage 和后一個 AutoreleasePoolPage,就是一個雙向鏈表,畢竟一個 AutoreleasePoolPage 能存放的對象是有限的。
5、depth:很明顯是這個鏈表有多深。
6、hiwat:一個在 DEBUG 時(shí)才有用的參數(shù),表示最高有記錄過多少對象(hi-water)。

@autoreleasepool{}
@autoreleasepool {
       __autoreleasing NSObject *obj = [NSObject new];
    }

偽代碼

    // 獲取哨兵POOL_SENTINEL
    void * atautoreleasepoolobj = objc_autoreleasePoolPush();
    {
        __autoreleasing NSObject *obj = [NSObject new];
    }
    // 就是release哨兵之后的autorelease對象。
    objc_autoreleasePoolPop(atautoreleasepoolobj);

autorelease調(diào)用棧

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └─ id objc_object::rootAutorelease2()
       └─ static id AutoreleasePoolPage::autorelease(id obj)
          └─ static id AutoreleasePoolPage::autoreleaseFast(id obj)
             ├─ id *add(id obj)
             ├─ static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
             │  ├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
             │  └─ id *add(id obj)
             └─ static id *autoreleaseNoPage(id obj)
                ├─ AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                └─ id *add(id obj)

方法流程圖


autorelease流程圖
各方法解析
1、id *add(id obj):解鎖加鎖,將 obj 存到 next 的位置,并將 next 加 1,典型的入棧操作
    id *add(id obj)
    {
        assert(!full());
        unprotect(); // 解鎖
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();// 加鎖
        return ret;
    }
2、id *autoreleaseNoPage(id obj)
    id *autoreleaseNoPage(id obj)
    {
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // (1)當(dāng)前page是一個占位符EMPTY_POOL_PLACEHOLDER,需要add(POOL_BOUNDARY)
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // 不使用autoreleasePool測試
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // (2)AutoreleasePoolPage的push,第一次autoreleaseFast(POOL_BOUNDARY)
            return setEmptyPoolPlaceholder();
        }

        // (3)
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            // (4)add(標(biāo)記位)
            page->add(POOL_BOUNDARY);
        }
        
        // (5)add(obj)
        return page->add(obj);
    }

三種執(zhí)行情況:
1、AutoreleasePoolPage的push時(shí)候:并沒有生成 AutoreleasePoolPage 對象,只是執(zhí)行(2)步驟--將 hot page 設(shè)置為 EMPTY_POOL_PLACEHOLDER占位符。
2、當(dāng)執(zhí)行push之后,第一個autorelease對象時(shí)候:執(zhí)行(1)(3)(4)(5)步驟。
3、當(dāng)沒有執(zhí)行push時(shí),第一個autorelease對象時(shí)候:執(zhí)行(3)(5)步驟,線程銷毀的時(shí)候會調(diào)用 pop。

3、 id *autoreleaseNewPage(id obj)

檢查 page 有沒有還沒滿的 child(順鏈表往下查),沒有的話就新建一個,再使用 add 函數(shù)將 obj 記錄

    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }
4、AutoreleasePoolPage的pop方法
    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        // (1)一個pool只是執(zhí)行push,沒有add對象時(shí)候
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            if (hotPage()) {
                pop(coldPage()->begin());
            } else {
                setHotPage(nil);
            }
            return;
        }

        // (2)根據(jù)token地址計(jì)算所在Page
        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) { // 檢查toekn是否等于POOL_BOUNDARY
            if (stop == page->begin()  &&  !page->parent) {
            } else {
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        // (3)循環(huán)直到到 stop 給每個對象調(diào)用 release
        page->releaseUntil(stop);

        // (4)如果現(xiàn)在這個 page 只剩下不到一半的空間了,則多留一個 child
        if (DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
一個autorelease對象在什么時(shí)刻釋放?

1、手動指定@autoreleasepool {}:當(dāng)前Autoreleasepool作用域大括號結(jié)束時(shí)釋放;

2、主線程不手動指定@autoreleasepool {}:autorelease對象會被添加到最近一次創(chuàng)建的autoreleasepool中,并在當(dāng)前的runloop迭代結(jié)束時(shí)候釋放。

3、子線程不手動指定@autoreleasepool {},autorelease對象時(shí)候會在當(dāng)前線程新建一個page(沒有哨兵POOL_BOUNDARY,所以在線程結(jié)束時(shí)候才能 page -> pop 釋放對象)

主Runloop對Autoreleasepool管理的流程:

1、當(dāng)前runloop狀態(tài)為kCFRunLoopEntry(進(jìn)入runloop) 時(shí),會調(diào)用push,由于此時(shí)優(yōu)先級最高,可以確保創(chuàng)建緩存池在其他回調(diào)之前。

2、當(dāng)前runloop狀態(tài)為kCFRunLoopBeforeWaiting(runloop即將休眠)時(shí),會先調(diào)用pop,再調(diào)用push。對應(yīng)著釋放舊池并創(chuàng)建新池,由于優(yōu)先級最低,這一操作也在其他回調(diào)之后。

3、當(dāng)前runloop狀態(tài)為kCFRunLoopExit(退出runloop) 時(shí),會調(diào)用pop,由于優(yōu)先級最低,此處可確保在其他回調(diào)完成后釋放緩存池。

autorelease 進(jìn)行的非持有方法的優(yōu)化

1、alloc/new/copy/mutableCopy---持有對象方法
2、其他類方法返回的對象,如果下面的createObj

@implementation BBObject
+ (instancetype)createObj {
    return [self new];
}

需要了解下面的方法:

id objc_autoreleaseReturnValue(id obj)
{
    // prepareOptimizedReturn判斷是否可以TSL優(yōu)化,可以則標(biāo)記,YES--就不需要調(diào)用 objc_autorelease(),優(yōu)化性能
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}
id objc_retainAutoreleasedReturnValue(id obj)
{
  // 如果之前 objc_autoreleaseReturnValue() 存入的標(biāo)志位為 ReturnAtPlus1,則直接返回對象,無需調(diào)用 objc_retain(),優(yōu)化性能
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
    return objc_retain(obj);
}


static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{
    assert(getReturnDisposition() == ReturnAtPlus0);

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

TLS 全稱為 Thread Local Storage(線程本地存儲),是每個線程專有的鍵值存儲,需要調(diào)用方與被調(diào)用方必須都是ARC的情況下(即全ARC環(huán)境下)

在某個線程上的函數(shù)調(diào)用棧上相鄰兩個函數(shù)對 TLS 進(jìn)行了存取,這中間肯定不會有別的程序『插手』。
所以 getReturnDisposition() 和 setReturnDisposition() 的實(shí)現(xiàn)比較簡單,不需要判斷考慮是針對哪個對象的 Disposition 進(jìn)行存取,因?yàn)楫?dāng)前線程上下文中只處理唯一的對象,保證不會亂掉。 

static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

callerAcceptsOptimizedReturn(__builtin_return_address(0))函數(shù)在不同架構(gòu)的 CPU 上實(shí)現(xiàn)也是不一樣的。
主要作用:

1、__builtin_return_address(0)獲取當(dāng)前函數(shù)返回地址。
2、callerAcceptsOptimizedReturn()方法判斷調(diào)用方是否緊接著調(diào)用了 objc_retainAutoreleasedReturnValue或者 objc_unsafeClaimAutoreleasedReturnValue
如果是就直接當(dāng)前對象地址,而不執(zhí)行retain與autorelease操作.

TLS優(yōu)化標(biāo)記

總結(jié):

MRC下:對象需要經(jīng)歷方法內(nèi)部new->內(nèi)部autorelease->外部retain->外部release這樣四步流程
ARC下:對象需要經(jīng)歷方法內(nèi)部new->外部release兩步,省了中間兩步“autorelease->retain-”
(TLS優(yōu)化其實(shí)與OC內(nèi)存管理“誰生產(chǎn)誰銷毀誰持有誰釋放”的黃金法則有所違背)

ARC 會視情況在調(diào)用方法時(shí)可能會添加 retain ,在方法內(nèi)部返回時(shí)可能會添加 autorelease ,經(jīng)過優(yōu)化后很可能會抵消。
autorelease 進(jìn)行的優(yōu)化
1、持有、無引用
- (void)test {
    [BBObject new];
}

編譯器編譯后的偽代碼

- (void)test {
    objc_release([BBObject new]) ;
}
2、持有、局部變量引用

__strong

- (void)test {
    __strong BBObject * obj = [BBObject new];
}

編譯器編譯后的偽代碼

- (void)test {
    id temp = [BBObject new];
    objc_storeStrong(&tmp,nil);//相當(dāng)于tmp指向?qū)ο髨?zhí)行release
}

__weak、__unsafe_unretained

    // 這種寫法xcode提示警告
    __weak BBObject * obj = [BBObject new]; 
    __unsafe_unretained BBObject * obj = [BBObject new];
3、持有、外部變量引用
- (void)test {
    self.obj = [BBObject new];
}

編譯器編譯后的偽代碼

- (void)test{
    id temp = [BBObject new];
    [self setObj:temp];//setter方法執(zhí)行objc_storeStrong
    objc_release(temp);
}
- (void)setObj:(id aObj) {
    objc_storeStrong(&_obj, aObj);
}
4、不持有、無引用
- (void)test {
    [BBObject createObj];
}

編譯器編譯后的偽代碼

+ (instancetype) createObj {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease] 
}
- (void)test { 
    objc_unsafeClaimAutoreleasedReturnValue([BBObject createObj]); 
}
5、不持有、局部變量引用

- (void)test {
    BBObject * obj1 = [BBObject createObj];
}

編譯器編譯后的偽代碼

+ (instancetype) createObj {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease] 
}
- (void)test {
    id obj1 = objc_retainAutoreleasedReturnValue([BBObject createObj]);  
    objc_storeStrong(& obj1,nil); 
}

發(fā)現(xiàn)obj1指向的對象不會加入autoreleasepool

6、不持有、外部變量引用
+ (instancetype) createObj {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 系統(tǒng)可能會調(diào)用[tmp autorelease] 
}
- (void)test {
    self.obj = [BBObject createObj];
}

編譯后的偽代碼

- (void)test {
    id tmp = _objc_retainAutoreleasedReturnValue([Foo createFoo]); 
    [self setObj:temp]; // setter方法執(zhí)行objc_storeStrong
    objc_release(temp);
}

查看autoreleasepool中的對象方法:
1、extern void _objc_autoreleasePoolPrint(void); //extern這個方法,需要查看的地方使用_objc_autoreleasePoolPrint();

2、需要查看的地方打斷點(diǎn),然后po _objc_autoreleasePoolPrint()


po _objc_autoreleasePoolPrint
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容