iOS-autorelease与autoreleasepool

Autoreleasepool自动释放池块提供了一个持有对象的所有权的机制,可以避免它立刻释放(如你从一个方法返回一个对象时).正常情况下,我们不需要创建自己的自动释放池块,但也有一些情况下,创建自动释放池是非常明智的(子线程开启新的任务,for循环生成大量对象的时候).

autorelease 与 runloop

autorelease 本质上就是延迟调用 release,实际上autorelease对象是在当前的runloop迭代结束时释放的,以下是在iOS 8.2模拟器中的测试代码:

_weak id reference = nil;
- (void)viewDidLoad {
   [super viewDidLoad];
   // Do any additional setup after loading the view, typically from a nib.
   NSArray *arr = [NSArray arrayWithObjects:@"FlyElephant",@"Keso", nil];
   reference = arr;
}

- (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
   NSLog(@"数组:%@",reference);
}

- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
    NSLog(@"数组:%@",reference);
}
FlyElephant.png

加入autorelease的测试代码:

@autoreleasepool {
        NSArray *arr = [NSArray arrayWithObjects:@"FlyElephant",@"Keso", nil];
        reference = arr;
    }
autoreleasepool.png

autoreleasepool 与 runloop

autoreleasepool与runloop乍一看没有关系,如果对Runloop有研究,对下面这段文字应该有印象:
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

autoreleasepool 原理

ARC下我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将进行编译:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

autoreleasepool最终底层是由autoreleasepoolpage实现,定义如下:

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

1.autoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
2.AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
3.AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小
4.next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
5.一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入.

autoreleasepool 实战

正常开发中也没有见过哪个项目中到处都是autoreleasepool的,那么什么时候使用autoreleasepool呢?

1.如果你写的程序不是基于UI框架的,比如说命令行工具.(较少)
2.如果创建一个循环,创建了大量的临时对象,你可以使用自动释放池处理在下一次迭代前处理这些对象,避免占用大量内存.
iOS中有三种循环比遍历方式for、forin、enumerateObjectsUsingBlcok,实际上enumerateObjectsUsingBlcok内部已经通过@autoreleasepool{}操作进行了对象处理,for和forin的方式需要我们自己手动处理,因此enumerateObjectsUsingBlcok效率最高,内存占用最少.
3.创建次级线程,当你创建线程的时候,你需要创建释放器避免内存泄漏.

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.

If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.

在Cocoa应用程序中每个系统线程都有自己的自动释放池来维护,如果手动创建创建线程,需要手动创建自动释放池.

如果创建常驻线程可能会导致大量的autorelease对象,应该像AppKit和UIkit一样使用autoreleasepool,如果你不使用cocoa的创建线程(比如通过POSIX创建线程),那么不需要使用autoreleasepool.

参考资料
官方文档
黑幕背后的Autorelease
深入理解RunLoop
Objective-C Autorelease Pool 的实现原理

你可能感兴趣的:(iOS-autorelease与autoreleasepool)