AutoReleasePool-自动释放池

255 阅读11分钟

AutoReleasePool

什么是AutoReleasePool? 先来举一个例子

@implementation KKPerson

- (void)dealloc {
    NSLog(@"KKPerson dealloc");
}

+ (instancetype)object {
    return [[KKPerson alloc] init];
}

- (void)testARCRule {
    
    __weak id tmp = nil;
    {
        KKPerson *person = [KKPerson object];
        tmp = person;
    }
    NSLog(@"tmp = %@", tmp);

}

此打印结果为:

tmp = <KKPerson: 0x6000026906b0>
KKPerson dealloc

如果稍作修改,那结果又将如何?

- (void)testARCRule {
    
    __weak id tmp = nil;
    {
        KKPerson *person = [[KKPerson alloc] init];
        tmp = person;
    }
    NSLog(@"tmp = %@", tmp);

}

此处结果为:

KKPerson dealloc
tmp = (null)

会出现上面的结果是因为这里有一个在ARC下的规则:alloc/new/copy/mutableCopy 开头的方法返回的对象不是 autorelease 对象,所以第一个例子中创建的对象并不会因为作用域的结束而先进行析构,此对象被加入到了AutoReleasePool(自动释放池)

这里可以再看一个例子:

@implementation KKPerson

+ (instancetype)object {
    return [[KKPerson alloc] init];
}

+ (NSString *)newKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
    
}


+ (NSString *)mutableCopyString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

+ (NSString *)allocKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
    //return @"Hello Logic";
}

+ (NSString *)copyKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

+ (NSString *)initKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

+ (NSString *)helloKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

+ (NSString *)createKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

- (NSString *)getKKString
{
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

-(NSString *)newInsKKString
{
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}
- (void)testARCRule {

    @autoreleasepool {
   
        __weak NSString *tmp1 = [KKPerson newKKString];
        __weak NSString *tmp2 = [KKPerson allocKKString];
        __weak NSString *tmp3 = [KKPerson copyKKString];
        __weak NSString *tmp4 = [KKPerson mutableCopyString];
        NSLog(@"%@", tmp1);
        NSLog(@"%@", tmp2);
        NSLog(@"%@", tmp3);
        NSLog(@"%@", tmp4);

        __weak NSString *tmp5 = [KKPerson initKKString];
        __weak NSString *tmp6 = [KKPerson helloKKString];
        __weak NSString *tmp7 = [KKPerson createKKString];
        __weak NSString *tmp8 = [[KKPerson new] getKKString];
        __weak NSString *tmp9 = [[KKPerson new] newInsKKString];
        NSLog(@"%@", tmp5);
        NSLog(@"%@", tmp6);
        NSLog(@"%@", tmp7);
        NSLog(@"%@", tmp8);
        NSLog(@"%@", tmp9);
    }
}

打印结果为:

(null)
(null)
(null)
(null)

Hello 12345
Hello 12345
Hello 12345
Hello 12345
(null)

这个结果便可以再次印证之前的观点,同时可以看出无论是类方法或者是实例方法均可适用,这里要注意Tagged Pointer以及常量字符串的问题,这里只是讨论对象

如果修改为以下这样

+ (NSString *)newKKString {
    return [[NSString alloc] initWithCString:"Hello 123" encoding:NSUTF8StringEncoding];
    
}

+ (NSString *)allocKKString {    
    return @"Hello 12345";
}

+ (NSString *)copyKKString {
    return [[NSString alloc] initWithCString:"Hello 12345" encoding:NSUTF8StringEncoding];
}

那么其类型已经发生了改变

image.png

MRC和ARC下的AutoReleasePool

自动释放池有两种使用方式:

  • NSAutoreleasePool:仅在MRC下使用
  • @autoreleasepool:MRCARC下均可使用

NSAutoreleasePool的使用

NSAutoreleasePool的三步:

  1. 生成一个NSAutoreleasePool对象
  2. 调用autorelease方法
  3. 对象销毁
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id object = [[NSObject alloc] init];
    [object autorelease];
    [pool drain];

`@autoreleasepool的使用

@autoreleasepool {
        
        NSObject *object = [[NSObject alloc] init];
    }

解析 AutoReleasePool

想要探究AutoReleasePool究竟是什么可以先创建一个空的工程后使用命令clang -rewrite-objc main.m将其文件改写为C++来查看,因为其入口便有一个自动释放池,不写任何东西看的可以更清楚

image.png

打开main.cpp进行查看 image.png

直接定位到这里

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_s5qd3zpx4ksg7ck7xj7mf24c0000gn_T_main_fef641_mi_0);
    }
    return 0;
}

重点看到__AtAutoreleasePool其实就是一个结构体,其中包含构造函数和析构函数

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

所以关注点聚焦到objc_autoreleasePoolPushobjc_autoreleasePoolPop

AutoreleasePoolPage

无论是push或者是pop其实都是调用自AutoreleasePoolPage,其实自动释放池就是一个对象,这个对象就是AutoreleasePoolPage

现在来看看其结构

SIZE表示其每一页的大小,从下方宏定义可以看出最大是16KB大小,最小是4KB

#define PAGE_MAX_SHIFT          14
#define PAGE_MAX_SIZE           (1 << PAGE_MAX_SHIFT)
#define PAGE_MAX_MASK           (PAGE_MAX_SIZE-1)

#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
#define PAGE_MIN_MASK           (PAGE_MIN_SIZE-1)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
    
private:
	static pthread_key_t const key = AUTORELEASE_POOL_KEY;
	static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
	static size_t const COUNT = SIZE / sizeof(id);
    static size_t const MAX_FAULTS = 2;

    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil

    ...
    //构造函数
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
            AutoreleasePoolPageData(begin(),
                                                            objc_thread_self(),
                                                            newParent,
                                                            newParent ? 1+newParent->depth : 0,
                                                            newParent ? newParent->hiwat : 0)
    {...}
   
    //析构函数
    ~AutoreleasePoolPage() 
    {...}

    // 每一页起始的位置
    id * begin() {...}
    // 每一页结束的位置
    id * end() {...}
   // 页中是否没有存入其它对象(页为空)
    bool empty() {...}
   // 页中是否已存满
    bool full() {...}
   // 页中存储的对象是否少于一半
    bool lessThanHalfFull() {...}
   // 添加对象
    id *add(id obj)
    {...}
   // 释放所有对象
    void releaseAll() 
    {...}
   // 释放stop之前位置所有的对象
    void releaseUntil(id *stop) 
    {...}

    
    // 释放当前线程私有空间
    static void tls_dealloc(void *p) 
    {...}
    // 获取AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {...}
    // 获取AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {...}

    // 是否有空释放池占位符
    static inline bool haveEmptyPoolPlaceholder()
    {...}
   // 设置空释放池占位符
    static inline id* setEmptyPoolPlaceholder()
    {...}
    // 获取当前操作page
    static inline AutoreleasePoolPage *hotPage() 
    {...}
    // 设置当前操作page
    static inline void setHotPage(AutoreleasePoolPage *page) 
    {...}
   // 获取到根page
    static inline AutoreleasePoolPage *coldPage() 
    {...}

   // error 快速释放?
    static inline id *autoreleaseFast(id obj)
    {...}
    // 添加对象时若是当前页存满的话,找到子节点中第一个未存满的页面进行存储
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {...}
    // 存储对象当没有page的时候调用此方法
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {...}

   // 创建新页
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {...}

public:
    // 自动释放
    static inline id autorelease(id obj)
    {...}

    // 压栈
    static inline void *push() 
    {...}
    
    //兼容老SDK的出栈
    __attribute__((noinline, cold))
    static void badPop(void *token)
    {...}
    // 出栈page
    template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {...}
    // 允许调试模式的出栈page
    __attribute__((noinline, cold))
    static void
    popPageDebug(void *token, AutoreleasePoolPage *page, id *stop)
    {...}
   // 出栈
    static inline void
    pop(void *token)
    {...}
  
   // 打印
    __attribute__((noinline, cold))
    void print()
    {...}
    // 打印所有信息
    __attribute__((noinline, cold))
    static void printAll()
    {...}


}

其继承于AutoreleasePoolPageData

struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

	magic_t const magic;
	__unsafe_unretained id *next;
	pthread_t const thread;
	AutoreleasePoolPage * const parent;
	AutoreleasePoolPage *child;
	uint32_t const depth;
	uint32_t hiwat;

	AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
	}
};

很容易看出自动释放池是以页为单位的双向链表结构

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() {
		OBJC_ASSERT(M1_len == strlen(M1));
		OBJC_ASSERT(M1_len == 3 * sizeof(m[1]));

		m[0] = M0;
		strncpy((char *)&m[1], M1, M1_len);
	}

	~magic_t() {
		// Clear magic before deallocation.
		// This prevents some false positives in memory debugging tools.
		// fixme semantically this should be memset_s(), but the
		// compiler doesn't optimize that at all (rdar://44856676).
		volatile uint64_t *p = (volatile uint64_t *)m;
		p[0] = 0; p[1] = 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
};

AutoreleasePoolPageData结构体:

  • magicmagic_t类型结构体,其占有内存为16字节
  • next指针占用8字节
  • thread指针占用8字节
  • parent指针占用8字节
  • child指针占用8字节
  • depth占用4字节
  • hiwat占用4字节

AutoreleasePoolPageData结构体占有56字节空间

objc_autoreleasePoolPush

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
static inline void *push() 
    {
        id *dest;
        if (slowpath(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;
    }

push方法中分为:

  • 没有创建自动释放池
  • 已经存在自动释放池

没有创建自动释放池

在没有创建自动释放池的情况下,调用autoreleaseNewPage(POOL_BOUNDARY),这里POOL_BOUNDARY就是哨兵对象主要起着标记边界的作用

static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
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;
    }
#   define AUTORELEASE_POOL_KEY  ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
static pthread_key_t const key = AUTORELEASE_POOL_KEY;

hotPage()方法中可以看到是根据key从当前线程取出AutoreleasePoolPage,每一个线程都会维护自己的自动释放池,每一个自动释放池对应一个线程,子线程在使用autorelease对象,懒加载出来一个autoreleasePoolPage

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;
            // 没有子节点的话则把page当做父节点来创建一个子节点
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        // 存放到当前线程中的私密空间
        setHotPage(page);
        return page->add(obj);
    }

autoreleaseFullPage(id obj, AutoreleasePoolPage *page)方法主要目的就是找到第一个没存满的页面将哨兵对象存入

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()) {
            //判断如果是空占位符则将设置压栈哨兵标识设置为true
            // 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) {//不是哨兵对象并且没有pool直接报错
            // 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", 
                         objc_thread_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 inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {// page存在并且未存满
            return page->add(obj);
        } else if (page) {// page存在但是已存满
            return autoreleaseFullPage(obj, page);
        } else {// page不存在
            return autoreleaseNoPage(obj);
        }
    }

这里主要在判断page存在如果当页未存满则直接存入哨兵对象,若已经存满就要通过链表找到第一个未存满的页进行存储。

不论是已经存在自动释放池或者没有创建自动释放池其中都会出现page->add(obj),所以这个方法肯定是很重要的

id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret;

#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        if (!DisableAutoreleaseCoalescing || !DisableAutoreleaseCoalescingLRU) {
            if (!DisableAutoreleaseCoalescingLRU) {
                if (!empty() && (obj != POOL_BOUNDARY)) {
                    AutoreleasePoolEntry *topEntry = (AutoreleasePoolEntry *)next - 1;
                    for (uintptr_t offset = 0; offset < 4; offset++) {
                        AutoreleasePoolEntry *offsetEntry = topEntry - offset;
                        if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) {
                            break;
                        }
                        if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) {
                            if (offset > 0) {
                                AutoreleasePoolEntry found = *offsetEntry;
                                memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry));
                                *topEntry = found;
                            }
                            topEntry->count++;
                            ret = (id *)topEntry;  // need to reset ret
                            goto done;
                        }
                    }
                }
            } else {
                if (!empty() && (obj != POOL_BOUNDARY)) {
                    AutoreleasePoolEntry *prevEntry = (AutoreleasePoolEntry *)next - 1;
                    if (prevEntry->ptr == (uintptr_t)obj && prevEntry->count < AutoreleasePoolEntry::maxCount) {
                        prevEntry->count++;
                        ret = (id *)prevEntry;  // need to reset ret
                        goto done;
                    }
                }
            }
        }
#endif
        ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        // Make sure obj fits in the bits available for it
        ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
#endif
     done:
        protect();
        return ret;
    }

这里看到*next++ = obj是将next指针指向栈顶对象,但是返回的ret则是前一个对象

objc_autoreleasePoolPop

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

pop这里带入的参数ctxt就是push后返回的哨兵对象,就是防止不应该出栈的对象也出栈

pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        //判断对象是否空占位符
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                //当前页不存在清除空占位符
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            //token设置为coldPage的begin()位置
            token = page->begin();
        } else {
            //找到token所在的页
            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);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }
        // 出栈页
        return popPage<false>(token, page, stop);
    }
template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();
        //出栈当前页对象
        page->releaseUntil(stop);

        // memory: delete empty children
        // 删除空的子节点
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            // 特殊情况:调试丢失的自动释放池时删除pop(top)的所有内容
            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();
            }
        }
    }
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
            // 每次都用hotPage() 重新开始,防止自动释放更多对象
            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();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

            // create an obj with the zeroed out top byte and release that
            id obj = (id)entry->ptr;
            int count = (int)entry->count;  // grab these before memset
#else
            // 出栈操作
            id obj = *--page->next;
#endif
            //置为SCRIBBLE,表示已经被释放
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                // release count+1 times since it is count of the additional
                // autoreleases beyond the first one
                for (int i = 0; i < count + 1; i++) {
                    objc_release(obj);
                }
#else
                objc_release(obj);
#endif
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }

releaseUntil方法是使用循环要找到stop对象,释放stop之前的所有对象,如同入栈的时候对next指针进行++操作,这里使用--来进行出栈操作,如果释放的对象不是哨兵对象则进行objc_release进行释放

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);
    }

kill()是先找到最后一个页面,也就是最后一个子节点然后往前开始不停的置空子节点,也就是删除child节点,直到当前页面

总结:

  • objc_autoreleasePoolPush压栈操作:

    • 没有自动释放池,只有空占位符在当前线程的私有空间时,创建页对象并压栈哨兵对象
    • 压栈普通的对象时是对next指针进行++操作来进行
    • page存满了的时候使用创建新的page并使用child指针进行关联
  • objc_autoreleasePoolPop出栈操作:

    • 出栈普通对象是对next指针进行--操作来进行
    • page对象都已经出栈时则设置parent节点page为当前操作页,直到找到stop对象

打印自动释放池

MRC模式下进行打印

extern void _objc_autoreleasePoolPrint();
 @autoreleasepool {
        
        for (int i = 1; i <= 3; i++) {
            Person *p = [[Person person] autorelease];
        }
        _objc_autoreleasePoolPrint();
    }

image.png

如果选择嵌套的话

@autoreleasepool {
        
        //Person *p = [Person person];
        for (int i = 1; i <= 1; i++) {
            Person *p = [[Person person] autorelease];
        }
        @autoreleasepool {
            for (int i = 1; i <= 2; i++) {
                Person *p = [[Person person] autorelease];
            }

            @autoreleasepool {
                for (int i = 1; i <= 3; i++) {
                    Person *p = [[Person person] autorelease];
                }
                _objc_autoreleasePoolPrint();
            }
        }
        
        
    }

image.png

由上面两个例子可以看出,哨兵对像是为了区别释放时的边界,每写出一个@autoreleasepool时会有一次push的操作,这里便是插入了一个哨兵对象

AutoReleasePool与线程的关系

__weak *id tmp = nil;
- (void)test_one {
    [NSThread detachNewThreadSelector:@selector(testAction) toTarget:self withObject:nil];
}
- (void)testAction {
    
    __autoreleasing id test = [NSObject new];
    NSLog(@"obj=%@", test);
    tmp = test;
    
    [[NSThread currentThread] setName:@"test runloop thread"];
    NSLog(@"thread ending");
}

tmp = test;处打上断点,lldb下输入watchpoint set variable tmp,并加上以下断点

image.png

image.png

image.png

image.png

image.png

image.png

线程在退出的时候会对自动释放池中的对象进行出栈操作,子线程在使用autorelease对象,懒加载出来一个autoreleasePoolPage,对于这点可以在autoreleaseNoPage方法中看到会创建一个对象后存入当前线程的空间中

static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }