runloop 内存管理机制

前言:

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++实现的类;看看里面的内容

 

图2

 

1.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,看图:

 

图4

 

AutoreleasePoolPop主要做一下事情:

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背后的“高人”。

参考:
http://www.cocoachina.com/ios/20141031/10107.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值