iOS內存管理之autorelease

微信掃一掃關注公個人衆號瞭解更多iOS知識、提高工做技能、大廠內推:bash


當你須要延遲調用release方法的時候會使用autorelease。如:微信

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}
複製代碼

在上面代碼中,經過alloc生成並持有對象,根據iOS內存管理規則,在失去對象的引用以前,咱們必需要放棄該對象的全部權。若是使用release,那麼對象在返回以前就會失去全部權,致使返回一個無用的對象。使用autorelease,會保證調用放在使用該返回值以前,該對象不會釋放,也就是說,autorelease能夠保證對象在跨越「方法調用邊界」後存活。框架

這時你可能會有一個疑問,使用autorelease的對象到底在何時釋放呢?接下來,咱們將會圍繞這個問題逐步進行研究。less

AutoreleasePool是如何工做的?

Coco框架會幫咱們維護一個autorelease pool用來將對象臨時保存在內存中,以便稍後釋放。函數

iOS應用程序是在event loop(事件循環)中運行的。當應用程序加載時,程序就會進入事件循環並等待用戶的交互事件。當一個點擊事件發生時,Cocoa框架會檢測到該事件,而後建立事件對象,並生成一個autorelease pool對象。整個流程以下表所示: oop

當事件處理方法完成執行時,控制權會返回給Cocoa框架。Cocoa框架會銷燬autorelease Pool,它會向池子中的每個對象都發送一條release消息。

autorelease實現

經過查看Objc源碼,來確認autorlease的實現:優化

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id objc_object::rootAutorelease()
{
    assert(!UseGC);

    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

id objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼

經過源碼能夠發現,當調用對象的autorelease方法時,最終調用的是AutoreleasePoolPage的autorelease方法。ui

接下來咱們看一下AutoreleasePoolPage的實現,this

AutoreleasePoolPage是使用C++實現的一個類,截取源碼中的主要部分,其實現以下:spa

class AutoreleasePoolPage 
{
#define POOL_SENTINEL nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);
    
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
    
    public:
    static inline id autorelease(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }


    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_SENTINEL);
        } else {
            dest = autoreleaseFast(POOL_SENTINEL);
        }
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        page = pageForPointer(token);
        stop = (id *)token;
        if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
            // This check is not valid with DebugPoolAllocation off
            // after an autorelease with a pool page but no pool in place.
            _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                        token);
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;
        *next++ = obj;
        protect();
        return ret;
    }

    void releaseAll() 
    {
        releaseUntil(begin());
    }
}
複製代碼

經過分析上面的源碼,咱們能夠了解到對象調用autorelease方法的過程以下:

  • 獲取正在使用的AutoreleasePoolPage對象。
  • 獲取當前AutoreleasePoolPage對象的next指針,將autorelease對象地址複製給next指針,next指針向上移動一個位置。
  • 若一個AutoreleasePoolPage空間被佔滿時,會新建一個AutoreleasePoolPage對象,連接鏈表,後來的autorelease對象會加入到新的page中。

AutoreleasePool的生命週期

在ARC下,咱們使用@autoreleasepool{}來使用AutoreleasePool:

@autoreleasepool {
}
複製代碼

使用clang(clang -rewrite-objc)命令將其轉化成C++代碼,關鍵代碼以下:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() 
  {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() 
  {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};
複製代碼

所以咱們能夠得出結論:Autorelease Pool的生命週期是由 void * objc_autoreleasePoolPush(void)和void objc_autoreleasePoolPop(void *)這兩個方法實現的。

經過查看Objc源碼,肯定這兩個方法的實現:

void *objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}
複製代碼

經過源碼,咱們能夠肯定,void * objc_autoreleasePoolPush(void)和 void objc_autoreleasePoolPop(void *)這兩個方法是對AutoreleasePoolPage::push()和AutoreleasePoolPage::pop(ctxt)方法的封裝。

經過分析AutoreleasePoolPage源碼中push()和pop()方法的實現,咱們能夠知道AutoreleasePool對象的生命週期

push()過程:

  • 獲取當前正在使用的AutoreleasePoolPage對象。若當前對象爲空,則生成一個新的page對象。若當前page存在且已滿,則建立一個新的page,經過鏈表的形式將兩個page連接起來。
  • 向當前page對象中next位置追加一個哨兵對象(POOL_SENTINEL)並返回該地址,這個地址做爲pop操做的入參。

**注意:**每調用一次push()操做都會建立一個新的autoreleasepool對象,即往AutoreleasePoolPage中插入一個POOL_SENTINEL,而且返回插入的 POOL_SENTINEL 的內存地址。

pop()過程:

  • 根據傳入的哨兵對象地址找到其所在的page。
  • 在當前page中,將晚於哨兵對象插入的全部autorelease對象都發送一次release消息,並將next指針移回正確位置。
  • 從最新加入的對象一直向前清理,能夠跨越若干個page,直到哨兵對象所在的位置。

###autorelease對象在何時釋放呢? 經過前面的分析,咱們能夠知道,autorelease對象是在AutoReleasePool的生命週期結束時釋放的。在沒有顯示生成AutoReleasePool對象的狀況下,autorelease對象在當前runloop迭代結束時釋放的,Cocoa框架會爲每一個runloop添加AutoreleasePool的push和pop操做。

###ARC下autorelease優化策略

先看一下,ARC下,調用NSMutableArray類的array方法時,編譯器爲咱們作了哪些操做

+ (id)array {
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    return objc_autoreleaseReturnValue(obj);
}

id obj = objc_retainAutoreleasedReturnValue([NSMutableArray array]);
複製代碼

objc_autoreleaseReturnValue函數會檢查使用該函數的方法或函數調用方的執行命令列表,若是方法或函數的調用方在調用了方法或函數後緊接着調用objc_retainAutoreleasedReturnValue()函數,那麼就不將返回的對象註冊到autoreleasepool中,而是直接傳遞到方法或函數的調用方。

拾遺

使用容器的block版本的枚舉器時,內部會自動添加一個AutoreleasePool

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 這裏被一個局部@autoreleasepool包圍着
}];
複製代碼

在普通for循環和for in循環中沒有。for循環和for in循環中遍歷產生大量autorelease變量時,就須要手加局部 AutoreleasePool啦。

相關文章
相關標籤/搜索