美文网首页IOSiOS Developer
iOS-autorelease与autoreleasepool

iOS-autorelease与autoreleasepool

作者: FlyElephant | 来源:发表于2017-05-19 17:49 被阅读217次

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

    autorelease 与 runloop

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

    <pre><code>`_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);
      }`</code></pre>

    FlyElephant.png

    加入autorelease的测试代码:
    <pre><code>@autoreleasepool { NSArray *arr = [NSArray arrayWithObjects:@"FlyElephant",@"Keso", nil]; reference = arr; }</code></pre>

    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,随后编译器将进行编译:
    <pre><code>void *context = objc_autoreleasePoolPush(); // {}中的代码 objc_autoreleasePoolPop(context);</code></pre>

    autoreleasepool最终底层是由autoreleasepoolpage实现,定义如下:
    <pre><code>class AutoreleasePoolPage { magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; };</code></pre>

    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

        本文链接:https://www.haomeiwen.com/subject/cmpyxxtx.html