一、autoreleasePool
在何时被释放?
App
启动后,苹果在主线程 RunLoop
里注册了两个 Observer
,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 Observer
监视的事件是 Entry(即将进入Loop)
,其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order
是 -2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer
监视了两个事件: BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit
(即将退出Loop) 时调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer
的 order
是 2147483647
,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer
回调内的。这些回调会被 RunLoop
创建好的 AutoreleasePool
环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool
了。
二、PerformSelector
的实现原理?
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
三、PerformSelector:afterDelay:
这个方法在子线程中是否起作用?为什么?怎么解决?
不起作用,子线程默认没有 Runloop
,也就没有 Timer
。
解决的办法是可以使用 GCD
来实现:Dispatch_after
四、为什么 NSTimer
有时候不好使?
因为创建的 NSTimer
默认是被加入到了 defaultMode
,所以当 Runloop
的 Mode
变化时,当前的 NSTimer
就不会工作了。
五、解释一下 GCD
在 Runloop
中的使用?
GCD
由 子线程 返回到 主线程,只有在这种情况下才会触发 RunLoop。会触发 RunLoop 的 Source 1 事件。
六、解释一下 NSTimer
。
NSTimer
其实就是 CFRunLoopTimerRef
,他们之间是 toll-free bridged
的。一个 NSTimer
注册到 RunLoop
后,RunLoop
会为其重复的时间点注册好事件。例如 10:00
, 10:10
, 10:20
这几个时间点。RunLoop
为了节省资源,并不会在非常准确的时间点回调这个Timer
。Timer
有个属性叫做 Tolerance
(宽容度),标示了当时间点到后,容许有多少最大误差。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。
CADisplayLink
是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source
)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer
相似),造成界面卡顿的感觉。在快速滑动 TableView
时,即使一帧的卡顿也会让用户有所察觉。Facebook
开源的 AsyncDisplayLink
就是为了解决界面卡顿的问题,其内部也用到了 RunLoop
。
七、等待补充中。。。
- Timer
- autoreleasePool创建
- 线程保活
- 检测卡顿
- scrollview的更新 TrackingRunloopMode下不做操作,添加到 defaultmode下