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. 微信小程序中使用echarts

    一.效果图 二.代码 import * as echarts from '../../component/ec-canvas/echarts'; const app = getApp(); var x ...

  2. What is state and props

    State, in React component, is internal dataset which affects the rendering of the component. To some ...

  3. 安装Yellowfin报错——No such file or directory: '/tmp/pip-build-jykvuD/YellowFin/README.md'

    https://blog.csdn.net/quqiaoluo5620/article/details/80608474 在Pycharm中安装Yellowfin时一直报错"no such ...

  4. Python3中assert断言

    一般的用法是: assert condition 用来让程序测试这个condition,如果condition为false,那么raise一个AssertionError.逻辑上等于: if not ...

  5. 显微镜下的webpack4入门

    前端的构建打包工具很多,比如grunt,gulp.相信这两者大家应该是耳熟能详的,上手相对简单,而且所需手敲的代码都是比较简单的.然后webpack的出现,让这两者打包工具都有点失宠了.webpack ...

  6. thinkPHP5搭建以及使用

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

  7. log4j动态文件名

    在项目中,对log的输出有多种多样的要求,下面具体分析一下动态log文件名输出的. 一,按照用户ID来生成log,这种情况,可以根据每个用户ID来动态生成logger. 代码如下: import or ...

  8. [Python3网络爬虫开发实战] 1.4.2-MongoDB安装

    MongoDB是由C++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容存储形式类似JSON对象,它的字段值可以包含其他文档.数组及文档数组,非常灵活. MongoDB支持多 ...

  9. python3.x Day6 多进程

    多进程:1.每个子进程申请到的资源都是独立的,不与其他进程共享.2.语法上和线程基本上差不多,使用multiprocessing.Process(target=xxxx,args=(xxx,xxx,x ...

  10. CSS——可视化格式模型

    CSS的可视化格式模型 CSS中规定每一个元素都有自己的盒子模型(相当一规定了这个元素如何显示): 然后可视化格式模型则是把这些盒子模型按照规则摆放到页面上,也就是如何布局: 换句话说,盒子模型规定了 ...