05.自动释放池与Runloop
Last updated
Last updated
为了探究 autoreleasepool 的原理,我们需要定位到源码。我们通过汇编断点试试
我们在 main 函数中添加断点:
这里我们发现 @autoreleasepool
关键字实际调用的是: objc_autoreleasePoolPush
。同时还发现一个与之匹配的 objc_autoreleasePoolPop
。
在源码中我们可以发现最终调用的是 AutoreleasePoolPage
的方法
一个线程的自动释放池是一个栈结构,存放的指针。每个指针要么是一个待释放的对象,要么是一个自动释放边界 POOL_BOUNDARY
。
一个 pool token
是一个指向该自动释放池的 POOL_BOUNDARY
的指针。
栈被绑定到一个双向链表。Pages
会按需进行增删。
POOL_BOUNDARY
边界的作用是防止访问到不该访问的内存区域
只在第一页中存在哨兵对象
这里使用双向链表的作用是, Page
能够互相找到
每页的内存地址并不是连续的
每页的容量,源码中有
1 << 12
先入后出
从栈顶开始通过内存平移进行处理
一个 Page
空了就将这个 Page
释放
并将上一个 Page
, 即父节点 设为 Hot
通过 alloc
new
copy
mutablecopy
other
前缀的方法创建的对象不会加入自动释放池。
使程序一直运行并接受用户输入
决定程序在何时应该处理那些事件
消息队列
节省CPU
时间
线程
和 RunLoop
之间是一一对应的(可嵌套),其关系是保存在一个全局的 Dictionary
里
线程结束时销毁, 或手动退出
Source
RunLoop
的数据源抽象类
RunLoop运行必须要在加入NSTimer或Source0、Sourc1、Observer输入后运行否则会直接退出
Source0: 处理App内部事件, App自行管理
包含了一个函数指针
,它并不能主动触发事件
调用 CFRunLoopSourceSignal(source)
,将这个 Source 标记为待处理
调用 CFRunLoopWakeUp(runloop)
来唤醒 RunLoop,让其处理这个事件
Source1: 由RunLoop
内核管理, MachPort
驱动(进程间通讯)
包含了一个 mach_port
和一个 函数指针
被用于通过内核和其他线程相互发送消息
如解锁/摇晃等
Timer
包含了时长 和 函数指针
Observer
向外部报告 RunLoop
状态的变化
Observer 与 AutoreleasePool(UIKit)
Observer
在RunLoop
两次Sleep
间 Pop
旧的并释放对象 Push
新的
Mode
在同一时间必须且只能在一个Mode
下运行
切换Mode
必须退出RunLoop
,重启
保证页面滑动流畅的关键
当创建一个
Timer
并加到DefaultMode
时,Timer
会得到重复回调,但此时滑动一个TableView
时,RunLoop
会将mode
切换为TrackingRunLoopMode
,这时Timer
就不会被回调,并且也不会影响到滑动操作。这两个Mode
都已经被标记为Common
特殊Mode
:
有时你需要一个
Timer
,在两个Mode
中都能得到回调,一种办法就是将这个Timer
分别加入这两个Mode
。还有一种方式,就是将Timer
加入到顶层的RunLoop
的commonModeItems
中。commonModeItems
被RunLoop
自动更新到所有具有Common
的Mode
里去。
App启动后,系统在主线程RunLoop 里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件 是 Entry(即将进入Loop)
其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池
。其优先级最高,保证创建
释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件
_BeforeWaiting(准备进入休眠)
时 _ 调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池
并 创建新池
;
_Exit(即将退出Loop)
时 _ 调用 _objc_autoreleasePoolPop()
来释放
自动释放池。
这个 Observer 优先级最低,保证其释放
池子发生在其他所有回调之后。
现在我们知道了AutoreleasePool
是在RunLoop即将进入RunLoop
和准备进入休眠
这两种状态的时候被创建
和销毁
的。
所以AutoreleasePool的释放有如下两种情况。
一是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。
二是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool