Runtime之AutoreleasePool小记

AutoreleasePool

  • 本文只是作者对于看Runtime源码时AutoreleasePool的一个问题记录及解答,并没有提供完整流程

  • autoreleasepool是和线程绑定的,一个线程对应一个autoreleasepool。而runloop和线程也是一一对应的,所以…

class AutoreleasePoolPage 
{
    // 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
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    // AutoreleasePoolPage的大小,通过宏定义,可以看到是4096字节
    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;//magic 用于对当前 AutoreleasePoolPage 完整性的校验
    
    // 一个AutoreleasePoolPage中会存储多个对象
    // next指向的是下一个AutoreleasePoolPage中下一个为空的内存地址(新来的对象会存储到next处)
    id *next;
    //(一个AutoreleasePoolPage属于一个线程,一个线程中可以有多个AutoreleasePoolPage)
    pthread_t const thread;//保存了当前页所在的线程
    
    // AutoreleasePoolPage是以双向链表的形式连接
    // 指向前一个节点
    AutoreleasePoolPage * const parent;
    // 指向后一个节点
    AutoreleasePoolPage *child;
    uint32_t const depth; //page的深度,首次为0,以后每次初始化一个page都加1
    uint32_t hiwat; //这个字段是high water的缩写,这个字段用来计算pool中最多存放的对象个数。在每次执行pop()的时候,会更新一下这个字段。

其中的注释也解释了AutoReleasePool的结构,如下56个字节存储以下信息,其他4096 - 56存注册到AutoReleasePool的对象信息
在这里插入图片描述

摘自@思锦_Leo 思锦_Leo
为什么有了ARC还要AutoreleasePool?
这个问题之前我也想过,搜了下,没有感觉回答满意的,也没找到苹果的官方回答,这里只能自给妄自推断一下。提到OC的RC,首先要横向对比一下Android的GC,GC的内存回收是集中式回收(定期回收),而RC的回收是伴随整个运行时的,所以android机器有种时“卡”时“流畅”的感觉,而iOS总体比较均匀,缺乏像GC的集中式回收内存的类似机制,所以猜测Pool的产生也是弥补RC的这一不足,在RC基础上进行内存优化的一种手段。
在这里插入图片描述

将对象添加到AutoreleasePoolPage中 add(id obj)

id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        // next = obj; next++;
        // 也就是将obj存放在next处,并将next指向下一个位置
        *next++ = obj;
        protect();
        return ret;//next-1
    }

objc_autoreleasePoolPop

AutoreleasePoolPage::pop(ctxt)
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);//获取当前 token 所在的 AutoreleasePoolPage
        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 (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);//释放栈中的对象,直到 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();
            }
        }
    }
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
//**可以从这段代码看出objc_autoreleasePoolPop的参数atautoreleasepoolobj为objc_autoreleasePoolPush()返回的值**

pop里面有个token参数,经过查找,发现其实际为指向POOL_BOUNDARY的指针,也就是每次push,pop之间的抬头,这也就刚好印证了

push(1)
	push(2)
		push(3)
		
		pop(3)
	pop(2)
pop(1)

AutoreleasePoolPush

这是push 方法中调用的一个方法

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 {
            // 如果page->child不为空,那么使用page->child
            if (page->child) page = page->child;
            // 否则的话,初始化一个新的AutoreleasePoolPage
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        // 将找到的合适的page设置成hotPage
        setHotPage(page);
        // 将对象添加到hotPage中
        return page->add(obj);
    }

上面提到的判断if (page->child) page = page->child; 为什么要判断一下有没有孩子呢,按理来说应该没有孩子的啊
原因如下:在pop的时候killpage时,如果page没超过一半满,就把其所有child杀了,否则,从page->child->child
开始杀,留下了page->child,所以以上。。。

static inline void pop(void *token) 
    {
        省略以上代码......
        else if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

线程与AutoreleasePool

在主线程的NSRunLoop对象的每个event loop开始之前,系统会自动创建一个autoreleasingPool,并在event loop结束时drain
另外,NSAutoreleasePool 中还提到,每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。

手动添加AutoreleasePool的情况

如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
如果你编写的循环中创建了大量的临时对象;
如果你创建了一个辅助线程。

什么时候释放

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

__weak NSArray   *string_weak_ = nil;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray __weak *string = [NSArray arrayWithObjects:@"123", nil];
    string_weak_ = string;
    NSLog(@"%@- 1", string_weak_); // 123
 }
 - (void)viewWillAppear:(BOOL)animated {
    NSLog(@"%@ - 2", string_weak_); //123
}
- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"%@- 3", string_weak_);//null
}

然后猜测viewDidLoad viewWillAppear是在同一个RunLoop中,而viewDidAppear可能在另一个线程

其他Autorelease相关知识点

使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
}];

当然,在普通for循环和for in循环中没有,所以,还是新版的block版本枚举器更加方便。for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool

参考文章

http://blog.sunnyxx.com/2014/10/15/behind-autorelease/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值