Autorelease机制是iOS开发人员管理对象内存的好伙伴。MRC中。调用[obj autorelease]来延迟内存的释放是一件简单自然的事;ARC下,我们甚至能够全然不知道Autorelease 系统就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢。一起来探究下Autorelease机制吧。

概述

当向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自己主动释放池。它仍然是个正当的对象,因此自己主动释放池定义的作用域内的其他对象能够向它发送消息。当自己主动释放池释放时,当中全部被管理对象都会收到”relrease”的消息, 从而池中的全部对象也就被释放。注意,同一个对象能够被多次调用”autorelease”方法,并能够放到同一个”AutoreleasePool”中。

所以引入这个自己主动释放池机制,对象的”autorelease”方法取代”relrease”方法能够延长它的生命周期,直接到当前”AutorelreasePool”释放。

iOS通过引用计数管理内存

OC 是通过”referring counting”(引用计数)的方式来管理内存的, 对象在開始分配内存(alloc)的时候引用计数为一,以后每当碰到有copy,retain的时候引用计数都会加一, 每当碰到release和autorelease的时候引用计数就会减一,假设此对象的计数变为了0, 就会被系统销毁.

GC(Garbage Collection) 即垃圾回收, 须要注意的是iOS没有垃圾回收机制的, 仅仅靠引用计数来进行管理内存, 这事实上也是 Autorelease 原理的核心. 大家不要混淆这样的方法和垃圾回收机制. 只是非常多语言还是有自己的垃圾回收机制的, 推荐一篇文章关于垃圾回收(GC)的三种基本方式.

NSAutoreleasePool

怎样使用

NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

当运行[pool autorelease]的时候,系统会进行一次内存释放,把autorelease的对象释放掉,假设没有NSAutoreleasePool , 那这些内存不会释放

注意,对象并非自己主动被增加到当前pool中。而是须要对对象发送autorelease消息。这样,对象就被加到当前pool的管理里了。当当前pool接受到drain消息时。它就简单的对它所管理的全部对象发送release消息。

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString* nsstring;
char* cstring = "Hello CString";
nsstring = [NSString stringWithUTF8String:cstring];
[pool drain];

注意事项

1.NSAutoreleasePool的管理范围是在NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];与[pool drain];之间的对象

2.在程序的入口main函数就调用NSAutoreleasePool,这样保证程序中不调用NSAutoreleasePool。但在退出时自己主动释放。新开线程最好实现NSAutoreleasePool

3.NSAutoreleasePool实际上是个对象引用计数自己主动处理器. NSAutoreleasePool能够同一时候有多个,它的组织是个栈,总是存在一个栈顶pool,也就是当前pool,每创建一个pool。就往栈里压一个,改变当前pool为新建的pool。然后,每次给pool发送drain消息,就弹出栈顶的pool,改当前pool为栈里的下一个 pool。

4.假设在Automatic Reference Counting(ARC) 不能直接使用autorelease pools,而是使用@autoreleasepool{}, @autoreleasepool{} 比直接使用NSAutoreleasePool 效率高。但在 MRC 下两者都是适用的.

5.在非 GC的引用计数环境下。drain和release一样,可是在garbage-collected环境中,使用drain。

(”release”与”drain”的差别是”drain”在有GC的环境中会引起GC回收操作。”release”反之。但在非GC环境中,两者同样。官方的说法是为了程序的兼容性。应该考虑用”drain”取代”release”。

)

Autorelease原理

如今看一段 MRC 下关于单层 autoreleasePool使用的代码:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray * array = [[[NSArray alloc] init] autorelease];
[pool drain];

AutoreleasePoolPage

ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool。随后编译器将其改写成以下的样子:

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

而这两个函数都是对AutoreleasePoolPage的简单封装。所以自己主动释放机制的核心就在于这个类。

AutoreleasePoolPage是一个C++实现的类

AutoreleasePool并没有单独的结构。而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别相应结构中的parent指针和child指针)。

AutoreleasePool是按线程一一相应的(结构中的thread指针指向当前线程)。

AutoreleasePoolPage每一个对象会开辟4096字节内存(也就是虚拟内存一页的大小)。除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。

上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置。

一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page增加。

所以,若当前线程中仅仅有一个AutoreleasePoolPage对象,并记录了非常多autorelease对象地址时。内存例如以下图:

上图中的情况。这一页再增加一个autorelease对象就要满了(也就是next指针立即指向栈顶),这时就要运行上面说的操作,建立下一页page对象,与这一页链表连接完毕后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶增加新对象。

所以。向一个对象发送- autorelease消息,就是将这个对象增加到当前AutoreleasePoolPage的栈顶next指针指向的位置。

释放时刻

每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象。值为0(也就是个nil),那么这一个page就变成了以下的样子:

objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址。被objc_autoreleasePoolPop(哨兵对象)作为入參。于是:

1、依据传入的哨兵对象地址找到哨兵对象所处的page。

2、在当前page中。将晚于哨兵对象插入的全部autorelease对象都发送一次- release消息,并向回移动next指针到正确位置。

3、补充2:从最新增加的对象一直向前清理,能够向前跨越若干个page,直到哨兵所在的page,刚才的objc_autoreleasePoolPop运行后。终于变成以下的样子:

嵌套的AutoreleasePool

但因为你提到了生成的每一个实例可能会比較大。仅仅在循环外嵌套,可能导致在pool释放前,内存里已经有10000个实例存在,造成瞬间占用内存过大的情况。

因此,假设你的每一个实例仅须要在单次循环过程中用到,那么能够考虑能够在循环内创建pool并释放

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 10000; i++)
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// ...
[pool drain];
}
[pool drain];

知道了上面的原理,嵌套的AutoreleasePool就非常easy了,pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已,就像剥洋葱一样。每次一层,互不影响。

Autorelease 释放时机

非常多人说, 当程序运行到作用域结束的位置时(当前作用域大括号结束时)。自己主动释放池就会被释放,这个说法是不正确的。正确的过程是怎样呢?

iOS的运行时是由一个一个runloop组成的,每一个runloop都会运行下图的一些步骤:

能够看到,每一个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,所以。普通情况下,每一个接受autorelease消息的对象。都会在下个runloop開始前被释放。也就是说,在一段同步的代码中运行过程中。生成的对象接受autorelease消息后,通常是不会在作用域结束前释放的。

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

小实验

__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad]; NSString *str = [NSString stringWithFormat:@"sunnyxx"]; // str是一个autorelease对象,设置一个weak的引用来观察它
reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; NSLog(@"%@", reference); // Console: sunnyxx}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]; NSLog(@"%@", reference); // Console: (null)}

因为这个vc在loadView之后便add到了window层级上,所以viewDidLoad和viewWillAppear是在同一个runloop调用的,因此在viewWillAppear中,这个autorelease的变量依旧有值。

当然,我们也能够手动干预Autorelease对象的释放时机:

- (void)viewDidLoad
{
[super viewDidLoad];
@autoreleasepool { NSString *str = [NSString stringWithFormat:@"sunnyxx"];
} NSLog(@"%@", str); // Console: (null)}

參考资料:Autorelease原理解析

75. Autorelease机制及释放时机的更多相关文章

  1. Objective-c中autorelease的释放时机

    如果你使用过MRR,autorelease这个关键字应该是太熟悉了,每次在我们生成一个新的对象返回时,都需要向这个对象发送autorelease消息,目的是为了延时释放创建的对象.那到底是在什么时候, ...

  2. Autorelease机制讲解

    Autorelease机制是在iOS内存管理中的一员.在MRC中,是通过调用[obj autorelease]来延迟内存释放:在ARC中,我们已经完全不需要知道Autorelease就能很好地管理好内 ...

  3. Autorelease Pool-自动释放池

    Autorelease Pool是Objective-C中的内存管理方式之一,它与线程和NSAutorelease类有关.每一个线程都拥有自己的Autorelease Pool栈,这个栈底层是由双向链 ...

  4. Autorelease对象什么时候释放?

    Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease ...

  5. 【原】你真的懂iOS的autorelease吗?

    或许这个题目起得有点太高调了,不过我只是想纠正一些童鞋对于autorelease的认识,如果能帮到几个人,那这篇文章也就值得了!当然,高手请绕道 本文主要探讨两个方面:(1)autorelease对象 ...

  6. 黑幕背后的Autorelease

    http://blog.sunnyxx.com/2014/10/15/behind-autorelease/ 我是前言 Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[o ...

  7. 内存管理总结-autoreleasePool

    转自其他 序言 无论是在MRC时期还是ARC时期,做过开发的程序员都接触过autoreleasepool.尽管接触过但本人对它还不是很了解.本文只是将自己的理解说出来.在内存管理的文章中提到了OC的内 ...

  8. 一起来找茬:记一起 clang 开启 -Oz 选项引发的血案

    作者:字节跳动终端技术 -- 刘夏 前言 笔者来自字节跳动终端技术 AppHealth (Client Infrastructure - AppHealth) 团队,在工作中我们会对开源 LLVM 及 ...

  9. Autorelease自动释放池的使用

    Autorelease自动释放池的使用 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加了retain.release和autorelease. MRC内存管理原则:谁申请,谁释放 遇到al ...

随机推荐

  1. CAD参数绘制样条线(com接口)

    在CAD设计时,需要绘制样条线,用户可以设置样条线线重及颜色等属性. 主要用到函数说明: _DMxDrawX::PathLineTo 把路径下一个点移到指定位置.详细说明如下: 参数 说明 DOUBL ...

  2. oracle优化器使用(oracle11g)

    一:优化器介绍 优化器(optimizer)是oracle数据库内置的一个核心子系统.优化器的目的是按照一定的判断原则来得到它认为的目标SQL在当前的情形下的最高效的执行路径,也就是为了得到目标SQL ...

  3. 【经验】停止Smart Card服务

    Windows+R键调出运行 输入 services.msc 有一项Smart Card的服务找到他->属性->启动类型(设置为禁用 )->确定,然后重新启动服务

  4. oracle分析函数系列之sum(col1) over(partition by col2 order by col3):实现分组汇总或递增汇总

    语法:sum(col1) over(partition by col2 order by col3 )  准备数据: DEPT_ID    ENAME          SAL1 1000       ...

  5. qrcode.js

    (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){thi ...

  6. thinkPHP5搭建以及使用

    0X01 Thinkphp 的安装 我这里选择的是使用 windows 下的 composer 进行安装,收下首先下载 composer 这个工具,安装完成以后进入我们想要创建项目的文件夹输入下面的命 ...

  7. LeetCode(4)Median of Two Sorted Arrays

    题目 There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the ...

  8. Symmetry

    Description The figure shown on the left is left-right symmetric as it is possible to fold the sheet ...

  9. 第二周习题O题

    Description   Given a graph (V,E) where V is a set of nodes and E is a set of arcs in VxV, and an or ...

  10. sql判断以逗号分隔的字符串中是否包含某个字符串--------MYSQL中利用select查询某字段中包含以逗号分隔的字符串的记录方法

    sql判断以逗号分隔的字符串中是否包含某个字符串---------------https://blog.csdn.net/wttykj/article/details/78520933 MYSQL中利 ...