Autoreleasepool

453 阅读7分钟

objc开源代码

(一)Autoreleasepool简介

AutoreleasePool:自动释放池是oc提供的一种自动回收的机制,具有延迟释放的特性,即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出{}或者超出[pool release]之后再被释放。

(二)alloc /init/new/copy/mutableCopy 返回的对象都不是autorelease对象

void createString(void) {
    
    NSString *taggedPointerStr = [[NSString alloc] initWithFormat:@"Hello"];//创建TaggedPointer对象
    NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];//创建常规对象
    NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];//创建Autorelease对象
    
    weak_TaggedPointerStr = taggedPointerStr;
    weak_allocString = string;
    weak_StringAutorelease = stringAutorelease;
    
    NSLog(@"------in the createString()------");
    NSLog(@"%@", weak_TaggedPointerStr);
    NSLog(@"%@", weak_allocString);
    NSLog(@"%@\n\n", weak_StringAutorelease);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.navigationItem.title = @"AotoReasePool";
    self.view.backgroundColor = [UIColor whiteColor];
    
    @autoreleasepool {
        createString();
        NSLog(@"------in the autoreleasepool------");
        NSLog(@"%@", weak_TaggedPointerStr);
        NSLog(@"%@", weak_allocString);
        NSLog(@"%@\n\n", weak_StringAutorelease);
    }
    NSLog(@"------in the main()------");
    NSLog(@"%@", weak_TaggedPointerStr);
    NSLog(@"%@", weak_allocString);
    NSLog(@"%@", weak_StringAutorelease);
}

输出:

 ------in the createString()------
 Hello
 Hello, World!
 Hello, World! Autorelease

 ------in the autoreleasepool------
 Hello
 (null)
 Hello, World! Autorelease

 ------in the main()------
 Hello
 (null)
 (null)

由上面的输出可以看出由allocHello一直存在,而由allocHello, World!创建的对象出了当前作用域以后就释放了。而有stringWithFormat创建Autorelease对象会延迟释放。这是为什么呢?

1.init/alloc/new/copy/mutableCopy创建的对象不是autorelease对象, 只作用在当前作用域。
2.当字符小于等于8个字符的时候,系统会对其进行优化为Tagged Pointer对象。Tagged Pointer通过在其最后一个bit位设置一个特殊标记,用于将数据直接保存在指针本身中。Tagged Pointer并不是真正的对象,使用时需要注意不要直接访问其isa变量。

(三) AutoreleasePoolPage的结构:

在了解如何实现之前,我们先了解一下什么是AutoreleasePoolPage

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
	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)
	{
	}
};

AutoreleasePoolPage它是一个结构体 magic:用来校验 AutoreleasePoolPage 的结构是否完整 next:指向栈顶,也就是最新入栈的autorelease对象的下一个位置 thread:指向当前线程 parent:指向父节点 child:指向子节点 depth:表示链表的深度,也就是链表节点的个数 hiwat:表示high water mark(最高水位标记),记录了入栈对象最多时候对象的个数。

一个线程的autoreleasepool就是一个指针栈,我们每次入栈之前都会添加一个POOL_BOUNDARY做为标记,然后添加对象。当autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。 这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。 autoreleasepool对应的线程存储了指向最新page。 画张图理解一下:

(四) AutoreleasePool的底层实现:

  • 如何添加对象
/* @autoreleasepool */ __AtAutoreleasePool __autoreleasepool; 

@autoreleasepool在底层会声明一个__AtAutoreleasePool类型的局部变量__autoreleasepool,那么我们来看看__AtAutoreleasePool的定义呢?

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

这是一个语法糖,构造函数在局部变量的开始调用,析构函数在作用域结束的时候调用。 那我们来看一下objc源码中这两个函数

void *
_objc_autoreleasePoolPush(void)
{
    return 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.
        //每一个释放池开始的时候都会创建一个新的page
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        //快速的添加哨兵对象nil
        dest = autoreleaseFast(POOL_BOUNDARY);//哨兵对象
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

我们来看一下,是如何添加对象的autoreleaseFast(POOL_BOUNDARY)POOL_BOUNDARY。我们称之为哨兵对象,查看它的宏,是一个nil

#   define POOL_BOUNDARY nil
static inline id *autoreleaseFast(id obj)
{
    //链表是有空间的
    AutoreleasePoolPage *page = hotPage();//回去最新的page
    if (page && !page->full()) {//1.存在page,并且没有存满,直接添加obj
        return page->add(obj);
    } else if (page) {//有page,但是满了,创建新的page,添加obj,child指针指向新创建page
        return autoreleaseFullPage(obj, page);
    } else {//没有page,创建一个page,并添加obj
        return autoreleaseNoPage(obj);
    }
}

如何向链表中添加objc,调用add(obj)这个函数

id *add(id obj)//像当前链表插入objc
{
    ASSERT(!full());
    unprotect();//解除保护
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;// 将obj入栈到栈顶并重新定位栈顶
    protect();
    return ret;
}

首先获取当前page没有就创建一个,然后添加一个哨兵对象POOL_BOUNDARY,之后在添加对象,将next指针指向最后一个位置。

  • 对象是如何释放的
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
static inline void
pop(void *token)//token指针指向栈顶的地址
{
    AutoreleasePoolPage *page;
    id *stop;
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
    } else {
        page = pageForPointer(token);// 通过栈顶的地址找到对应的page
    }
    //部分代码省略
    return popPage<false>(token, page, stop);
}

拿到当前的page进行pop

static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    if (allowDebug && PrintPoolHiwat) printHiwat();// 记录最高水位标记

    page->releaseUntil(stop);// 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象

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

page->releaseUntil(stop)

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

autorelease函数和push函数一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,不过push函数的入栈的是一个哨兵对象,而autorelease函数入栈的是需要加入autoreleasepool的对象。

(五)使用场景

1、对象作为函数返回值
+ (instancetype)object{
    return [[MMPooLPerson alloc] init];
}

当一个对象要作为函数返回值的时候,因为要遵循谁申请谁释放的思想,所以应该在返回之前释放,但要是返回之前释放了,就会造成野指针错误,但是要是不释放,那么就违背了谁申请谁释放的原则,所以就可以使用autorelease延迟释放的特性,将其在返回之前做一次autorelease,加入到自动释放池中,保证可以被返回,一次runloop之后系统会帮我们释放。

2、临时生成大量对象

一定要将自动释放池放在for循环里面,要释放在外面,就会因为大量对象得不到及时释放,而造成内存紧张,最后程序意外退出

for (int i = 0; i<10000; i++) {
    @autoreleasepool {
        UIImageView *imegeV = [[UIImageView alloc]init];
        imegeV.image = [UIImage imageNamed:@"efef"];
        [self.view addSubview:imegeV];
    }
}
3、创建新的线程
4、长时间在后台运行的任务

总结

@autoreleasepool

系统就是通过@autoreleasepool {}这种方式来为我们创建自动释放池的,一个线程对应一个runloop,系统会为每一个runloop隐式的创建一个自动释放池,所有的autoreleasePool构成一个栈式结构,在每个runloop结束时,当前栈顶的autoreleasePool会被销毁,而且会对其中的每一个对象做一次release(严格来说,是你对这个对象做了几次autorelease就会做几次release,不一定是一次)。

系统方法那个会帮我们自动写入自动释放池
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
自动释放池多层嵌套

嵌套的autoreleasepool:autorelease对象被添加进离他最近的自动释放池,多层的pool会有多个哨兵对象。

@autoreleasepool {//p1被放在该自动释放池里面
    Person *p1 = [[Person alloc]init];
    @autoreleasepool {//p2被放在该自动释放池里面
        Person *p2 = [[Person alloc]init];
    }
}
NSThread、NSRunLoop 和 NSAutoreleasePool的关系

1.每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。 2.每一个autoreleasepool对应一个线程,一个线程会有多个autoreleasepool 3.每一个线程会维护自己的autoreleasepoolNSRunLoop对象的每个 event loop 开始前,系统会自动创建一个 autoreleasepool,并在 event loop 结束时 drain