Autorelease Pool學習筆記

參考

自動釋放池的前世今生 ---- 深入解析 Autoreleasepool

你真的懂iOS的autorelease嗎?

@autoreleasepool-內存的分配與釋放

Autorelease Pool是什么

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

新建一個工程,總會見到這樣的幾行代碼,這行代碼將所有的事件、消息全部交給了UIApplication來處理。

同時也說明,整個iOS應用,是包含在一個自動釋放池block中的。

編譯的時候,這段代碼會被轉換成

{
    __AtAutoreleasePool __autoreleasepool;
}

其中,出現的結構體__AtAutoreleasePool

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

這表明,我們的main函數實際執行了

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        // do whatever you want
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

結論 @autoreleasepool 只是幫助我們少寫了這兩行代碼而已,讓代碼看起來更美觀,然后要根據上述兩個方法來分析自動釋放池的實現。

Autorelease Pool的主要結構

每一個autorelease pool都是由一系列的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;
};

可見,自動釋放池的AutoreleasePoolPage是以雙向鏈表的結構連接起來的

而在自動釋放池的內存中,AutoreleasePoolPage被以棧結構存儲起來,如下圖

AutoreleasePoolPage在內存中的結構

更多詳細實現,請移步自動釋放池的前世今生 ---- 深入解析 Autoreleasepool

MRC與ARC時代的Autorelease

MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),分別對應著手動引用計數和自動引用計數。

對!是計數,不是“ GC、垃圾回收 ”什么的,就是說,在Objective-C的開發中,ARC不代表像Java那樣有GC做垃圾回收,所以本質上還是要“手動”管理內存的。也就是說,我們在ARC環境下寫的代碼,不用自己手動插入“ retain、release這些消息 ”,ARC會在編譯時為我們在合適的位置插入,釋放不必要的內存。

@autoreleasepool 就跟對象的release密切相關。

MRC時代,如果我們想先retain一個對象,但是并不知道在什么時候可以release它,我們可以像下面這么做:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [[[NSString alloc] initWithString:@"tutuge"] autorelease];
//use str...
[pool release];
//str is released

就是說,我們可以在創建對象的時候給對象發送“ autorelease”消息,然后當 NSAutoreleasePool結束的時候,“標記過”autorelease的對象都會被“ release”掉,也就是會被釋放掉。

但是在ARC時代,我們不用手動發送autorelease消息,ARC會自動幫我們加。而這個時候,@autoreleasepool做的事情,跟 NSAutoreleasePool就一模一樣了。

Autorelease對象的釋放時機

實踐內容可以參考 你真的懂iOS的autorelease嗎?

在上面引用的文章中,筆者用__weak id指針指向一個autorelease對象,在不增加引用的情況下觀察autorelease對象的釋放情況。

  • 首先在viewDidLoad方法中初始化一個Array對象
  • 分別嘗試在viewWillAppearviewDidAppeare方法中觀測Array的值

發現,在viewWillAppear方法中,對象未被釋放,而到了viewDidAppear中就被釋放了,發現Array對象并非超出作用域就馬上被釋放。得出結論,autorelease并不是根據對象的作用域來決定釋放時機。

實際上,autorelease釋放對象的依據是Runloop,簡單說,runloop就是iOS中的消息循環機制,當一個runloop結束時系統才會一次性清理掉被autorelease處理過的對象,其實本質上說是在本次runloop迭代結束時清理掉被本次迭代期間被放到autorelease pool中的對象的。至于何時runloop結束并沒有固定的duration。

擴展

既然由runloop來決定對象釋放時機而不是作用域,那么,在一個{}內使用循環大量創建對象就有可能帶來內存上的問題,大量對象會被創建而沒有及時釋放,這時候就需要靠我們人工的干預autorelease的釋放了。

上文有提到autorelease pool,一旦一個對象被autorelease,則該對象會被放到iOS的一個池:autorelease pool,其實這個pool本質上是一個stack,扔到pool中的對象等價于入棧。我們把需要及時釋放掉的代碼塊放入我們生成的autorelease pool中,結束后清空這個自定義的pool,主動地讓pool清空掉,從而達到及時釋放內存的目的。以上述圖片處理的例子為例,優化如下:

for (int i = 0; i <= 1000; i ++) {
    //創建一個自動釋放池
    NSAutoreleasePool *pool = [NSAutoreleasePool new];//也可以使用@autoreleasePool{domeSomething}的方式
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"PNG"];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
    UIImage *image2 = [image imageByScalingAndCroppingForSize:CGSizeMake(480, 320)];
    [image release];
    //將自動釋放池內存釋放,它會同時釋放掉上面代碼中產生的臨時變量image2
    [pool drain];
}

這樣,每次循環結束時,可以及時的釋放臨時對象的內存,其中,對自動釋放池的操作可以用上文提到的方法來替代。

@autoreleasePool{
    //domeSomeThing;
}

什么時候用@autoreleasepool

根據 Apple的文檔 ,使用場景如下:

  • 寫基于命令行的的程序時,就是沒有UI框架,如AppKitCocoa框架時。
  • 寫循環,循環里面包含了大量臨時創建的對象。(本文的例子)
  • 創建了新的線程。(非Cocoa程序創建線程時才需要)
  • 長時間在后臺運行的任務。

什么對象會加入Autoreleasepool中

  1. 使用alloc、newcopymutableCopy的方法進行初始化時,由系統管理對象,在適當的位置release
  2. 使用array會自動將返回值的對象注冊到Autoreleasepool。
  3. __weak修飾的對象,為了保證在引用時不被廢棄,會注冊到Autoreleasepool中。
  4. id的指針或對象的指針,在沒有顯示指定時會被注冊到Autoleasepool中。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.1 什么是自動引用計數 概念:在 LLVM 編譯器中設置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,265評論 1 17
  • 1、[NSObject alloc]在創建完對象后,會讓該對象的retainCount+1,后續的init為初始化...
    naiyi閱讀 1,556評論 0 4
  • 11.看下面的程序,第一個NSLog會輸出什么?這時str的retainCount是多少?第二個和第三個呢? 為什...
    AlanGe閱讀 757評論 1 4
  • 內存管理 簡述OC中內存管理機制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,013評論 1 16
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,217評論 30 472