Autoreleasepool的理解及原理
之前师父问我对autoreleasepool有什么理解?当时的并没有研究过autoreleasepool,只知道在程序启动时会建立一个autoreleasepool,但不清楚autoreleasepool运行的原理。那么今天就来总结一波。
接下来将从以下几个问题出发。
1、什么时候,什么对象会加入autoreleasepool;
2、autoreleasepool的创建与销毁;
3、autoreleasepool的原理;
一、什么对象什么时候加入autoreleasepool?
autorelease 对象在创建时会被加入autoreleasepool中。
那么什么是autorelease对象呢?
如果不是通过alloc、new、retain、copy 创建出来的对象都是autorelease对象。
比如:"NSString *str = [NSString stringWithFormat:@"xiaoyueyue"],类方法创建的对象str为autorelease对象。
如果是通过alloc、new、retain、copy 创建出来的对象怎么进行处理?(通过arc内部进行引用计数来进行管理的,此问题待深究)
二、autoreleasepool的创建与销毁
autoreleasepool的创建与销毁可分为两种情况:
系统自动创建与销毁
自动创建:
在进入程序入口时系统会在main函数中自动创建autoreleasepool。
自动销毁:
(1).自动创建的autoreleasepool会在runloop交替的间隔中销毁,autoreleasepool释放池中对象。
(2).在autoreleasepool被撑满时销毁,autoreleasepool释放池中对象。
runloop什么时候会发生交替呢?
从程序启动到加载完成再到等待用户交互,此过程是一个完成的runloop。当用户再次进行交互时会重新启动一个新的runloop。这里理解的比较浅显了,待深入之后再详细说明
手动创建与销毁
手动创建:通过@autoreleasepool{}方法创建autoreleasepool.
举个栗子,在for循环中创建大量临时变量时,手动创建autoreleasepool来管理for循环产生的变量,如下:
int largeNum = 1024 * 1024 *2; for (int i = 0; I < largeNum; I ++ ){ @autoreleasepool{ NSString *str = [NSString stringWithFormat:@"hello"]; str = [str uppercaseString]; str = [NSString stringByAppendingFormat:@"World"]; } NSLog(@"this is test"); }
手动创建autoreleasepool的销毁
autorelease对象在什么时候释放取决于autoreleasepool什么时候销毁,autoreleasepool什么时候销毁又取决于autoreleasepool的作用域,
autorelease对象是在autoreleasepool所在作用域执行结束时释放的,autoreleasepool所在作用域执行结束,autoreleasepool被销毁,然后释放池中对象。
原来以为,autorelease对象在@autoreleasepool{}执行完立即释放,但是这种想法是有问题的。比如说在上图for循环中的autoreleasepool,@autoreleasepool{}执行完成时,autoreleasepool并没有释放,当NSlog()执行结束,本次循环结束下一次循环开始时,即autoreleasepool所在作用域执行结束时,autoreleasepool被销毁,autoreleasepool释放池中对象。
思考:程序入口已经为我们创建autoreleasepool,我们自己为什么还要创建autoreleasepool呢?
举个栗子,在上图(手动创建autoreleasepool)的for循环中,每次for循环中创建的字符串str对象都是不同的(可以通过查看str的内存地址来验证NSLog(@”%p”,str)),所有在for循环中的创建字符串对象需要等到循环结束时才会被释放。
这时就会消耗大量的内存资源,存在内存问题。这时就需要我们自己来创建autoreleasepool了。主线程或者是GCD中的线程,这些线程默认都有 autoreleasepool,除此之外,在主线程外开启子线程时,需要我们手动创建autoreleasepool来管理autorelease对象。因为主线程的的runloop默认是开启的,子线程的runloop默认是关闭的,也就意味着子线程中没有autoreleasepool,子线程中的autorelease对象就没有办法进行释放,这就会造成内存泄漏。
三、自动释放池的原理
autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成.
- objc_autoreleasepoolPush
- objc_autoreleasepoolPop
- objc_autorelease
对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。
上面的push和pop两个函数都是对AutoreleasePoolPage的简单封装
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(哨兵对象)作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
- 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:
以上总结摘抄自几个不同的博客,多数是sunnyxx的博客,很感谢大神们的分享,我理解的差不多的时候,也总结出来再做一次分享,同样也是方便自己复习。