蘋果的AutoreleasePool

AutoreleasePool

App啟動后,蘋果在主線程RunLoop 里注冊了兩個Observer,其回調都是_wrapRunLoopWithAutoreleasePoolHandler()

第一個 Observer 監視的事件是 Entry(即將進入Loop),其回調內會調用_objc_autoreleasePoolPush() 創建自動釋放池。其 order 是-2147483647,優先級最高,保證創建釋放池發生在其他所有回調之前。

第二個 Observer 監視了兩個事件:BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 釋放舊的池并創建新池;Exit(即將退出Loop) 時調用_objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observerorder 是 2147483647,優先級最低,保證其釋放池子發生在其他所有回調之后。

在主線程執行的代碼,通常是寫在諸如事件回調、Timer回調內的。這些回調會被 RunLoop創建好的AutoreleasePool環繞著,所以不會出現內存泄漏,開發者也不必顯示創建 Pool 了。

從 main 函數開始

main 函數可以說是在整個 iOS 開發中非常不起眼的一個函數,它很好地隱藏在Supporting Files 文件夾中,卻是整個 iOS 應用的入口。
main.m 文件中的內容是這樣的:

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

在這個 @autoreleasepool block 中只包含了一行代碼,這行代碼將所有的事件、消息全部交給了UIApplication來處理,但是這不是本文關注的重點。
需要注意的是:整個 iOS 的應用都是包含在一個自動釋放池 block 中的。

@autoreleasepool

@autoreleasepool 到底是什么?我們在命令行中使用clang -rewrite-objc main.m讓編譯器重新改寫這個文件:

$ clang -rewrite-objc main.m

找到 main.cpp 文件
在這個文件中,有一個非常奇怪的__AtAutoreleasePool的結構體,前面的注釋寫到 /* @autoreleasepopl */。也就是說 @autoreleasepool {} 被轉換為一個 __AtAutoreleasePool結構體:

{
    __AtAutoreleasePool __autoreleasepool;
}

想要弄清楚這行代碼的意義,我們要在main.cpp中查找名為 __AtAutoreleasePool的結構體:

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

這個結構體會在初始化時調用 objc_autoreleasePoolPush()方法,會在析構時調用 objc_autoreleasePoolPop方法。
這表明,我們的 main 函數在實際工作時其實是這樣的:

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();

        // do whatever you want

        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

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

AutoreleasePool 是什么

這一節開始分析方法 objc_autoreleasePoolPushobjc_autoreleasePoolPop 的實現:

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

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

上面的方法看上去是對 AutoreleasePoolPage 對應靜態方法 pushpop 的封裝。

這一小節會按照下面的順序逐步解析代碼中的內容:

AutoreleasePoolPage 的結構

AutoreleasePoolPage 是一個 C++ 中的類:
它在 NSObject.mm 中的定義是這樣的:

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

magic用于對當前AutoreleasePoolPage 完整性的校驗
thread保存了當前頁所在的線程
每一個自動釋放池都是由一系列的 AutoreleasePoolPage組成的,并且每一個 AutoreleasePoolPage 的大小都是 4096 字節(16 進制 0x1000)

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

雙向鏈表

自動釋放池中的 AutoreleasePoolPage 是以雙向鏈表的形式連接起來的:


圖片.png

parent 和 child 就是用來構造雙向鏈表的指針。

releaseUntil 釋放對象

releaseUntil 方法的實現如下:

void releaseUntil(id *stop) {
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();

        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_SENTINEL) {
            objc_release(obj);
        }
    }

    setHotPage(this);
}

它的實現還是很容易的,用一個 while 循環持續釋放 AutoreleasePoolPage 中的內容,直到next指向了 stop 。
使用 memset 將內存的內容設置成SCRIBBLE,然后使用 objc_release 釋放對象。

kill() 方法

會將當前頁面以及子頁面全部刪除:

void kill() {
    AutoreleasePoolPage *page = this;
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

autorelease 方法

- [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 方法的調用棧中,最終都會調用上面提到的 autoreleaseFast 方法,將當前對象加到 AutoreleasePoolPage 中。

這一小節中這些方法的實現都非常容易,只是進行了一些參數上的檢查,最終還要調用 autoreleaseFast 方法:

inline id objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

小結

  • 自動釋放池是由 AutoreleasePoolPage 以雙向鏈表的方式實現的
  • 當對象調用autorelease方法時,會將對象加入AutoreleasePoolPage 的棧中
  • 調用AutoreleasePoolPage::pop 方法會向棧中的對象發送 release 消息
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容