前言:
Autorelease机制对于iOS开发人员对对象的内存管理省下不少心血,说白了就是你甭管内存的管理问题,我会在背后帮你处理,不需要你操碎了心去避雷,这就是ARC的最大的好处!正所谓任何事情都是一把双刃剑,还有一个block里面循环引用的雷池要注意的,如何注意其实很简单,在这就不说了。
runloop:
runloop是什么的东西,runloop就好比一个特务,接到任务就去处理,没任务就休假,等待任务,任务有很多类型的。相对于线程,做完一个任务就挂掉了,runloop还是有很大的优势的,先看看runloop的大概流程图:
图1上图的2-9流程就是这个”特务“的处理任务,休假,接到任务作处理,处理完后再休假(等待任务)的一个循环,我说了这么多,跟标题的内容有什么关系呢?关系是有的,本篇重点就在第6点里面了。
图中第1条的 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
这个用我的话称为“外围释放池”的创建!
图中第6 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
图中第10 Observer 监视事件是exit(即讲退出runloop),其回调内会调用 _objc_autoreleasePoolpop() 释放自动释放池。
这个用我的话称为“外围析放池”的释放!
那么第6步的优先级低的释放池无论被创建和释放多少次也对“外围释放池”没影响了,也符合了这个逻辑思维了!
好了,上面的内容是对标题的内容作一个铺垫,那么我今天要深入说的就是第6步监听的回调的实现原理,也就是Autorelease的原理
各位,先想想这个“Autorelease对象什么时候被切底释放?”
答:当前作用域大括号结束时释放。答案真的是这样吗?也许手动添加Autorelease pool是这样的!
真正的答案是:在非手动添加Autorelease pool下,Autorelease对象是在当前runloop进入休眠等待前被释放的,为何会这样,接下来一一探究。
Autorelease内部原理
Autoreleasepool{} 编译后是这样的:
void * context = objc_autoreleasePoolPush();
{}// 内部要做的事情
objc_autoreleasePoolPush(context);
这两个方法是对AutoreleasePoolPage的一个简单封装,其核心就在这个类里面,AutoreleasePoolPage是一个C++实现的类;看看里面的内容
图21.AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)。
2.AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)。
3.AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。
4.上面的id *next指针作为游标指向栈顶最新注册进来的autorelease对象的下一个位置。
5.一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表(用结构中的parent指针和child指针分别指向旧的AutoreleasePoolPage和新的AutoreleasePoolPage),后来的autorelease对象在新的page加入。
当我们创建好注册进来的对象就变成这样
图3当next 指针在栈顶的低一度内存时,当再注册一个对象进来时,那么系统就会创建一个新的AutoreleasePoolPage来保存对象,新的parent指针指向旧的AutoreleasePoolPage,形成链表的结构一样,保证释放和新建AutoreleasePool能有序进行。每向一个对象发送 autorelease 消息,就是把对象加入到next 指针处,next 指针向高内存偏移一个位置(位置大小取决于新添加进来的对象内存的大小) 这是自动释放对象add进来的大概原理。
释放原理
好了,自动释放对象加进来的大概原理我们清楚了,那么反过来,对象释放的原理也差不多了。。。 慢着,释放时究竟我要释放到那个位置呢?凭借什么去标记呢?
内涵在于每当objc_autoreleasePoolPush被执行时,runtime会向当前AutoreleasePoolPage加入一个标记,称为“哨兵指针”,(我很喜欢这个名字)这个哨兵指针内容是nil,看图:
图4AutoreleasePoolPop主要做一下事情:
1.AutoreleasePoolPop要传入的参数就是这个哨兵指针,首先找到这个哨兵指针所在的page。
2.在next指针和哨兵指针之间的对象都发送一次 release消息,并把next指针移动上一次设立哨兵指针的位置,(由于page结构类似于链表,所有这一步可以靠parent指针跨越多个page的对象作处理)
以上就是Autorelease内部的实现原理。
我形象点说就是page就是一个气泵,next指针就是那个活动的活塞,哨兵指针地址就是那个顶点,当我释放对象时就等于我往里面压缩,把活动的活塞推到顶点,里面的空气就是add进来的对象,经过活动的活塞的顶点(next指针)向活塞推到顶点(哨兵指针位置)靠近时,空气(对象被释放)被排出,当活动的活塞(next指针)被退回到活塞顶点(哨兵指针位置),两者之间的空气被排出去了(对象被释放了)这时候就不在推了,这时候当再有对象add进来时就像有空气吹进来,把活动的活塞的顶点(next指针)和活塞推到顶点(哨兵指针位置)一点一点隔开,一到AutoreleasePoolPop时又以“推活塞“的形式去释放对象了。
好了,总结上下问所说的内容:当一个runloop在不停的循环工作,那么runloop每一次循环必定会经过BeforeWaiting(准备进入休眠):而去BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池,那么这两个方法来销毁要释放的对象,所以我们根本不需要担心Autorelease的内存管理问题,这就是ARC背后的“高人”。
最后,文章一些内容引用到一些大神级别的博客的内容,如有侵权,请及时联系。
由于水平有限,仅供大家学习 (大神请无视)如有错误,请指正!
最后的最后贴上一些更深入了解的博客地址:
网友评论