iOS - 自動(dòng)釋放池與@autoreleasepool

一、官網(wǎng)關(guān)于自動(dòng)釋放池的說(shuō)明截取

NSAutoreleasePool

  • NSAutoreleasePool 類被用來(lái)支持自動(dòng)引用計(jì)數(shù)內(nèi)存管理系統(tǒng)。一個(gè)自動(dòng)釋放池存儲(chǔ)的對(duì)象當(dāng)自己被銷毀的時(shí)會(huì)向其中的對(duì)象發(fā)送 release 消息。

Overview

1.png
  • 在一個(gè)自動(dòng)引用計(jì)數(shù)的環(huán)境中(并不是垃圾回收機(jī)制),一個(gè)包含了多個(gè)對(duì)象的 NSAutoreleasePool 對(duì)象能夠接收 autorelease 消息并且當(dāng)銷毀它的時(shí)候會(huì)對(duì)每一個(gè)池子中的對(duì)象發(fā)送 release 消息。因此,發(fā)送 autorelease 而不是 release 消息延長(zhǎng)了對(duì)象的生命周期直到 pool 被清空的時(shí)候(當(dāng)對(duì)象被保留的時(shí)候會(huì)更久)。一個(gè)對(duì)象能夠被放到同一個(gè)池子中許多次,在這種情況下每放一次都會(huì)收到一個(gè) release 消息。

  • 在引用計(jì)數(shù)的環(huán)境中,Cocoa 期望有一個(gè)自動(dòng)釋放池能夠保持有效。如果一個(gè)池子沒(méi)有用了,需要自動(dòng)釋放的對(duì)象沒(méi)有被釋放從而會(huì)造成內(nèi)存泄漏。在這種情況下,你的程序?qū)?huì)報(bào)錯(cuò)。

  • Application Kit 在事件循環(huán)開(kāi)始的時(shí)候在主線程創(chuàng)建了一個(gè)自動(dòng)釋放池,并且在結(jié)束的時(shí)候去清空它,從而釋放所有進(jìn)程事件中生成的自動(dòng)釋放的對(duì)象。如果使用了 Application Kit ,就沒(méi)必要再去創(chuàng)建自己的自動(dòng)釋放池。然而,如果你的應(yīng)用在事件循環(huán)中創(chuàng)建了很多臨時(shí)的自動(dòng)釋放的對(duì)象,創(chuàng)建臨時(shí)的自動(dòng)釋放池會(huì)將有助于削減你內(nèi)存峰值的占用。

  • 你創(chuàng)建了一個(gè) NSAutoreleasePool 對(duì)象根據(jù) alloc 和 init 消息并且用 drain 來(lái)清空它。因?yàn)槟悴荒軌虮A粢粋€(gè)自動(dòng)釋放池(或者自動(dòng)釋放它。), 清空一個(gè)池子最終會(huì)影響它的銷毀。你應(yīng)該在創(chuàng)建它的同一個(gè)上下文來(lái)進(jìn)行銷毀的工作。

  • 每一個(gè)線程(包括主線程)包含一個(gè)它自己的自動(dòng)釋放池對(duì)象的堆棧。作為一個(gè)新的被創(chuàng)建的池子,它們被添加到堆棧的頂部。當(dāng)池子被釋放的時(shí)候,它們從棧中被移除。自動(dòng)釋放的對(duì)象被放在當(dāng)前線程的自動(dòng)釋放池的頂部。當(dāng)一個(gè)線程終止的時(shí)候,它自動(dòng)清空與它關(guān)聯(lián)的所有的自動(dòng)釋放池。

線程

  • 如果你想要 Cocoa 在 ApplicationKit 的主線程之外調(diào)用,比如你創(chuàng)建了一個(gè) Foundation 的 應(yīng)用或者你創(chuàng)建了一個(gè)線程,你需要?jiǎng)?chuàng)建你自己的自動(dòng)釋放池。
  • 如果應(yīng)用或線程是長(zhǎng)久保存的并且潛在的生成了很多自動(dòng)釋放的對(duì)象,這時(shí)應(yīng)該定期的清空并且創(chuàng)建自動(dòng)釋放池(就像 Application Kit 在主線程中做的那樣);否則,對(duì)象的積累會(huì)增加內(nèi)存的占用。如果,獨(dú)立的線程并沒(méi)有使用 Cocoa 的調(diào)用,你沒(méi)有必要去創(chuàng)建一個(gè)自動(dòng)釋放池。

注意
如果使用了 POSIX 線程 APIS 而不是 NSThread 對(duì)象來(lái)創(chuàng)建線程,你不能使用 Cocoa,包括 NSautoreleasePool,除非 Cocoa 是在多線程模式下,Cocoa 進(jìn)入了多線程模式只有在首次創(chuàng)建 NSThread 對(duì)象的時(shí)候,為了在第二個(gè) POSIX 線程中使用 Cocoa ,你的應(yīng)用必須首先至少創(chuàng)建了一個(gè)獨(dú)立的 NSThread 對(duì)象,這個(gè)對(duì)象可以立即退出。你可以通過(guò) NSThread 類方法 isMultiTheraded 來(lái)測(cè)試 Cocoa 是否在多線程模式下。

垃圾回收

  • 在垃圾回收的環(huán)境下,是不需要自動(dòng)釋放池的。你可能寫了一個(gè) framework ,它被設(shè)計(jì)用來(lái)在垃圾回收環(huán)境和引用計(jì)數(shù)環(huán)境下都可工作。在這種情況下,你可以使用自動(dòng)釋放池去提示回收器回收可能是合適的。在垃圾回收環(huán)境中,如果必要會(huì)發(fā)送一個(gè) drain 消息到池子中去觸發(fā)垃圾回收機(jī)制;然而,release,是一個(gè)空操作。在引用計(jì)數(shù)的環(huán)境中,drain 和 release 的效果是一樣的。通常,你應(yīng)該使用 drain 而不是 release。

二、什么時(shí)候是用 @autoreleasepool

  • 寫基于命令行的的程序時(shí),就是沒(méi)有UI框架,如 AppKit 等 Cocoa 框架時(shí)。
  • 當(dāng)我們的應(yīng)用有需要?jiǎng)?chuàng)建大量的臨時(shí)變量的時(shí)候,可以是用 @autoreleasepool 來(lái)減少內(nèi)存峰值。
  • 為什么?
    自動(dòng)釋放池可以延長(zhǎng)對(duì)象的聲明周期,如果一個(gè)事件周期很長(zhǎng),比如有一個(gè)很長(zhǎng)的循環(huán)邏輯,那么一個(gè)臨時(shí)變量可能很長(zhǎng)時(shí)間都不會(huì)被釋放,一直在內(nèi)存中保留,那么內(nèi)存的峰值就會(huì)一直增加,但是其實(shí)這個(gè)臨時(shí)變量是我們不再需要的。這個(gè)時(shí)候就通過(guò)創(chuàng)建新的自動(dòng)釋放池來(lái)縮短臨時(shí)變量的生命周期來(lái)降低內(nèi)存的峰值。
  • 這是一個(gè)說(shuō)明這個(gè)問(wèn)題的很好的例子。
  • YYKit 中的使用
for (int i = 0; i < count; i++) {
@autoreleasepool {
    id imageSrc = _images[i];
    NSDictionary *frameProperty = NULL;
    if (_type == YYImageTypeGIF && count > 1) {
    frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *)     kCGImagePropertyGIFDelayTime:_durations[i]}};
    } else {
      frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality :       @(_quality)};
    }
    if ([imageSrc isKindOfClass:[UIImage class]]) {
    UIImage *image = imageSrc;
    if (image.imageOrientation != UIImageOrientationUp && image.CGImage) {
    CGBitmapInfo info = CGImageGetBitmapInfo(image.CGImage) |         CGImageGetAlphaInfo(image.CGImage);
    CGImageRef rotated = YYCGImageCreateCopyWithOrientation(image.CGImage,     image.imageOrientation, info);
    if (rotated) {
      image = [UIImage imageWithCGImage:rotated];
      CFRelease(rotated);
    }
  }
    if (image.CGImage) CGImageDestinationAddImage(destination, ((UIImage     *)imageSrc).CGImage, (CFDictionaryRef)frameProperty);
    } else if ([imageSrc isKindOfClass:[NSURL class]]) {
      CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageSrc, NULL);
    if (source) {
      CGImageDestinationAddImageFromSource(destination, source, i,     (CFDictionaryRef)frameProperty);
      CFRelease(source);
  }
  } else if ([imageSrc isKindOfClass:[NSData class]]) {
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageSrc, NULL);
        if (source) {
          CGImageDestinationAddImageFromSource(destination, source, i,       (CFDictionaryRef)frameProperty);
          CFRelease(source);
        }
      }
    }
}

三、release 和 drain的區(qū)別

  • 當(dāng)我們向自動(dòng)釋放池 pool 發(fā)送 release 消息,將會(huì)向池中臨時(shí)對(duì)象發(fā)送一條 release 消息,并且自身也會(huì)被銷毀。
  • 向它發(fā)送drain消息時(shí),將會(huì)向池中臨時(shí)對(duì)象發(fā)送一條release消息。
  • 官方解釋
  • release:
    釋放并且出棧接收者。(ARC)
  • drain:
    • 在引用計(jì)數(shù)環(huán)境中,會(huì)釋放并且出棧接受者。
    • 在垃圾回收環(huán)境中,會(huì)觸發(fā)垃圾回收機(jī)制如果上次分配的內(nèi)存集合大于當(dāng)前的閾值。

四、Autoreleasepool 底層實(shí)現(xiàn)

  • 首先我們?nèi)タ梢圆榭匆幌拢琧lang 轉(zhuǎn)成 c++ 的 autoreleasepool 的源碼:
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;
};

可以發(fā)現(xiàn)objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 這兩個(gè)方法。

  • 再看一下runtime 中 Autoreleasepool 的結(jié)構(gòu),通過(guò)閱讀源碼可以看出 Autoreleasepool 是一個(gè)由 AutoreleasepoolPage 雙向鏈表的結(jié)構(gòu),其中 child 指向它的子 page,parent 指向它的父 page。


    雙向鏈表結(jié)構(gòu)圖
  • 并且每個(gè) AutoreleasepoolPage 對(duì)象的大小都是 4096 個(gè)字節(jié)。
#define PAGE_MAX_SIZE           PAGE_SIZE
#define PAGE_SIZE       I386_PGBYTES
#define I386_PGBYTES        4096        /* bytes per 80386 page */
  • AutoreleasepoolPage 通過(guò)壓棧的方式來(lái)存儲(chǔ)每個(gè)需要自動(dòng)釋放的對(duì)象。
//入棧方法
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            //在 Debug 情況下每一個(gè)自動(dòng)釋放池 都以一個(gè)新的 poolPage 開(kāi)始
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
//正常情況下,調(diào)用 push 方法會(huì)先插入一個(gè) POOL_BOUNDARY 標(biāo)志位
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
  • 然后我們來(lái)看看 runtime 中的源碼
/***********************************************************************
 自動(dòng)釋放池的實(shí)現(xiàn):
 一個(gè)線程的自動(dòng)釋放池是一個(gè)指針堆棧
 每一個(gè)指針或者指向被釋放的對(duì)象,或者是自動(dòng)釋放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自動(dòng)釋放池的邊界。
 一個(gè)池子的 token 是指向池子 POOL_BOUNDARY 的指針。當(dāng)池子被出棧的時(shí)候,每一個(gè)高于標(biāo)準(zhǔn)的對(duì)象都會(huì)被釋放掉。
 堆棧被分成一個(gè)頁(yè)面的雙向鏈表。頁(yè)面按照需要添加或者刪除。
 本地線程存放著指向當(dāng)前頁(yè)的指針,在這里存放著新創(chuàng)建的自動(dòng)釋放的對(duì)象。
**********************************************************************/
// Set this to 1 to mprotect() autorelease pool contents
//將這個(gè)設(shè)為 1 可以通過(guò)mprotect修改映射存儲(chǔ)區(qū)的權(quán)限來(lái)更改自動(dòng)釋放池的內(nèi)容
#define PROTECT_AUTORELEASEPOOL 0
#define CHECK_AUTORELEASEPOOL (DEBUG)
BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));
namespace {
//對(duì)AutoreleasePoolPage進(jìn)行完整性校驗(yàn)
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];    
    magic_t() {
        assert(M1_len == strlen(M1));
        assert(M1_len == 3 * sizeof(m[1]));
        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }
    ~magic_t() {
        m[0] = m[1] = m[2] = m[3] = 0;
    }
    bool check() const {
        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
    }
    bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
        return check();
#else
        return (m[0] == M0);
#endif
    }
#   undef M1
};
//自動(dòng)釋放頁(yè)
class AutoreleasePoolPage 
{
  //EMPTY_POOL_PLACEHOLDER 被存放在本地線程存儲(chǔ)中當(dāng)一個(gè)池入棧并且沒(méi)有存放任何對(duì)象的時(shí)候。這樣在棧頂入棧出棧并且沒(méi)有使用它們的時(shí)候會(huì)節(jié)省內(nèi)存。
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
#   define POOL_BOUNDARY 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
  //對(duì)象數(shù)量
    static size_t const COUNT = SIZE / sizeof(id);
//校驗(yàn)完整性
    magic_t const magic;
//頁(yè)中對(duì)象的下一位索引
    id *next;
//線程
    pthread_t const thread;
//父頁(yè)
    AutoreleasePoolPage * const parent;
//子頁(yè)
    AutoreleasePoolPage *child;
//深度
    uint32_t const depth;
    uint32_t hiwat;
    // SIZE-sizeof(*this) bytes of contents follow
//創(chuàng)建
    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
//刪除
    static void operator delete(void * p) {
        return free(p);
    }
//設(shè)置當(dāng)前內(nèi)存可讀
    inline void protect() {
#if PROTECT_AUTORELEASEPOOL
        mprotect(this, SIZE, PROT_READ);
        check();
#endif
    }
//設(shè)置當(dāng)前內(nèi)存可讀可寫
    inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
        check();
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }
  //初始化
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
        : magic(), next(begin()), thread(pthread_self()),
          parent(newParent), child(nil), 
          depth(parent ? 1+parent->depth : 0), 
          hiwat(parent ? parent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            assert(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }
//析構(gòu)
    ~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        assert(empty());
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        assert(!child);
    }
//被破壞的
    void busted(bool die = true) 
    {
        magic_t right;
        (die ? _objc_fatal : _objc_inform)
            ("autorelease pool page %p corrupted\n"
             "  magic     0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  pthread   %p\n"
             "  should be %p\n", 
             this, 
             magic.m[0], magic.m[1], magic.m[2], magic.m[3], 
             right.m[0], right.m[1], right.m[2], right.m[3], 
             this->thread, pthread_self());
    }
//校驗(yàn)
    void check(bool die = true) 
    {
        if (!magic.check() || !pthread_equal(thread, pthread_self())) {
            busted(die);
        }
    }
//快速校驗(yàn)
    void fastcheck(bool die = true) 
    {
#if CHECK_AUTORELEASEPOOL
        check(die);
#else
        if (! magic.fastcheck()) {
            busted(die);
        }
#endif
    }
//頁(yè)的開(kāi)始位置
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
//頁(yè)的結(jié)束位置
    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }
//頁(yè)是否是空的
    bool empty() {
        return next == begin();
    }
//頁(yè)是否是滿的
    bool full() { 
        return next == end();
    }
//是否少于一半
    bool lessThanHalfFull() {
        return (next - begin() < (end() - begin()) / 2);
    }
//添加對(duì)象
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
//釋放所有對(duì)象
    void releaseAll() 
    {
        releaseUntil(begin());
    }
//釋放到 stop 的位置之前的所有對(duì)象
    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
            while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();
            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
            page->unprotect();
            id obj = *--page->next;
//將頁(yè)索引內(nèi)容置為 SCRIBBLE 表示已經(jīng)被釋放
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();
            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }
        setHotPage(this);
#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            assert(page->empty());
        }
#endif
    }
//殺死
    void kill() 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        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);
    }
//釋放本地線程存儲(chǔ)空間
    static void tls_dealloc(void *p) 
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }
        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);
        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) pop(page->begin());  // pop all of the pools
            if (DebugMissingPools || DebugPoolAllocation) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }
//獲取 AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        uintptr_t offset = p % SIZE;
        assert(offset >= sizeof(AutoreleasePoolPage));
        result = (AutoreleasePoolPage *)(p - offset);
        result->fastcheck();
        return result;
    }
//是否有空池占位符
    static inline bool haveEmptyPoolPlaceholder()
    {
        id *tls = (id *)tls_get_direct(key);
        return (tls == EMPTY_POOL_PLACEHOLDER);
    }
//設(shè)置空池占位符
    static inline id* setEmptyPoolPlaceholder()
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
//獲取當(dāng)前頁(yè)
    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
//設(shè)置當(dāng)前頁(yè)
    static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }
//獲取 coldPage
    static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }
//快速釋放
    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }
//添加自動(dòng)釋放對(duì)象,當(dāng)頁(yè)滿的時(shí)候調(diào)用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that 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);
    }
//添加自動(dòng)釋放對(duì)象,當(dāng)沒(méi)頁(yè)的時(shí)候使用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        assert(!hotPage());
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _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) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }
        // We are pushing an object or a non-placeholder'd pool.
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        // Push the requested object or pool.
        return page->add(obj);
    }
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
//公開(kāi)方法
public:
//自動(dòng)釋放
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }
//入棧
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
//兼容老的 SDK 出棧方法
    static void badPop(void *token)
    {
        // Error. For bincompat purposes this is not 
        // fatal in executables built with old SDKs.
        if (DebugPoolAllocation  ||  sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) {
            // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
            _objc_fatal
                ("Invalid or prematurely-freed autorelease pool %p.", token);
        }
        // Old SDK. Bad pop is warned once.
        static bool complained = false;
        if (!complained) {
            complained = true;
            _objc_inform_now_and_on_crash
                ("Invalid or prematurely-freed autorelease pool %p. "
                 "Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
                 "Proceeding anyway because the app is old "
                 "(SDK version " SDK_FORMAT "). Memory errors are likely.",
                     token, FORMAT_SDK(sdkVersion()));
        }
        objc_autoreleasePoolInvalid(token);
    }
//出棧
    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }
        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }
//打印 hiwat
        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();
            }
        }
    }
    static void init()
    {
        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                             AutoreleasePoolPage::tls_dealloc);
        assert(r == 0);
    }
//打印
    void print() 
    {
        _objc_inform("[%p]  ................  PAGE %s %s %s", this, 
                     full() ? "(full)" : "", 
                     this == hotPage() ? "(hot)" : "", 
                     this == coldPage() ? "(cold)" : "");
        check(false);
        for (id *p = begin(); p < next; p++) {
            if (*p == POOL_BOUNDARY) {
                _objc_inform("[%p]  ################  POOL %p", p, p);
            } else {
                _objc_inform("[%p]  %#16lx  %s", 
                             p, (unsigned long)*p, object_getClassName(*p));
            }
        }
    }
//打印所有
    static void printAll()
    {        
        _objc_inform("##############");
        _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
        AutoreleasePoolPage *page;
        ptrdiff_t objects = 0;
        for (page = coldPage(); page; page = page->child) {
            objects += page->next - page->begin();
        }
        _objc_inform("%llu releases pending.", (unsigned long long)objects);
        if (haveEmptyPoolPlaceholder()) {
            _objc_inform("[%p]  ................  PAGE (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
            _objc_inform("[%p]  ################  POOL (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
        }
        else {
            for (page = coldPage(); page; page = page->child) {
                page->print();
            }
        }

        _objc_inform("##############");
    }
//打印 hiwat 
    static void printHiwat()
    {
        // Check and propagate high water mark
        // Ignore high water marks under 256 to suppress noise.
        AutoreleasePoolPage *p = hotPage();
        uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
        if (mark > p->hiwat  &&  mark > 256) {
            for( ; p; p = p->parent) {
                p->unprotect();
                p->hiwat = mark;
                p->protect();
            }
            _objc_inform("POOL HIGHWATER: new high water mark of %u "
                         "pending releases for thread %p:", 
                         mark, pthread_self());
            void *stack[128];
            int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
            char **sym = backtrace_symbols(stack, count);
            for (int i = 0; i < count; i++) {
                _objc_inform("POOL HIGHWATER:     %s", sym[i]);
            }
            free(sym);
        }
    }
#undef POOL_BOUNDARY
};

參考文章
官方文檔

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

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