共计 5886 个字符,预计需要花费 15 分钟才能阅读完成。
1. 为什么 NSTimer 有时候不好使?
因为创立的 NSTimer
默认是被退出到了 defaultMode
,所以当 Runloop
的 Mode
变
化时,以后的 NSTimer
就不会工作了。
2.AFNetworking 中如何使用 Runloop?
RunLoop
启动前外部必须要有至多一个 Timer/Observer/Source
,所以AFNetworking
在[runLoop run]
之前先创立了一个新的NSMachPort
增加进去了。
通常状况下,调用者须要持有这个 NSMachPort
(mach_port
) 并在内部线程通过这个
port
发送音讯到 loop
内;但此处增加port
只是为了让 RunLoop
不至于退出,并没有用于理论的发送音讯。
当须要这个后盾线程执行工作时, AFNetworking
通过调用
[NSObject performSelector:onThread:..]
将这个工作扔到了后盾线程的 RunLoop
3.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 了。
4.PerformSelector:afterDelay: 这个办法在子线程中是否起作用?为什么?怎么解决?
不起作用,子线程默认没有 Runloop
,也就没有 Timer
。
解决的方法是能够应用 GCD
来实现: Dispatch_after
5.RunLoop 的 Mode
首先作为一个开发者,有一个学习的气氛跟一个交换圈子特地重要,这是一个我的 iOS 开发公众号:编程大鑫,不论你是小白还是大牛都欢送入驻,让咱们一起提高,独特倒退!(群内会收费提供一些群主珍藏的收费学习书籍材料以及整顿好的几百道面试题和答案文档!)**
对于 Mode
首先要晓得一个 RunLoop
对象中可能蕴含多个 Mode
,且每次调用 RunLoop
的主函数时,只能指定其中一个 Mode(CurrentMode)
。切换 Mode
,须要从新指定一个 Mode
。次要是为了分隔开不同的 Source
、Timer
、Observer
,让它们之间互不影响。
当 RunLoop
运行在 Mode1
上时,是无奈承受解决 Mode2
或 Mode3
上的 Source Timer Observer
事件的总共是有五种 CFRunLoopMode
:
kCFRunLoopDefaultMode
:默认模式,主线程是在这个运行模式下运行UITrackingRunLoopMode
:跟踪用户交互事件(用于ScrollView
追踪触摸滑动,保障界面滑动时不受其余 Mode
影响)
UIInitializationRunLoopMod
:在刚启动 App
时第进入的第一个Mode
,启动实现后就不再应用GSEventReceiveRunLoopMode
:承受零碎外部事件,通常用不到kCFRunLoopCommonModes
:伪模式,不是一种真正的运行模式,是同步 Source/Timer/Observer 到多个 Mode 中的一种解决方案
6.RunLoop 的实现机制
对于 RunLoop
而言最外围的事件就是保障线程在没有音讯的时候休眠,在有音讯时唤醒,以进步程序性能。 RunLoop
这个机制是依附零碎内核来实现的(苹果操作系统外围组件 Darwin
中的 Mach
)。
RunLoop
通过mach_msg
() 函数接管、发送音讯。它的实质是调用函数mach_msg_trap
(),相当于是一个零碎调用,会触发内核状态切换。在用户态调用mach_msg_trap
() 时会切换到内核态;内核态中内核实现的mach_msg
() 函数会实现理论的工作。
即基于 port
的 source1
,监听端口,端口有音讯就会触发回调;而 source0
,要手动标记为待处理和手动唤醒 RunLoop
大抵逻辑为:
1、告诉观察者 RunLoop
行将启动。
2、告诉观察者行将要解决 Timer
事件。
3、告诉观察者行将要解决 source0
事件。
4、解决 source0
事件。
5、如果基于端口的源 (Source1)
筹备好并处于期待状态,进入步骤 9。
6、告诉观察者线程行将进入休眠状态。
7、将线程置于休眠状态,由用户态切换到内核态,直到上面的任一事件产生才唤醒线程。
(1)一个基于 port
的 Source1
的事件(图里应该是 source0
)。
(2)一个 Timer
到工夫了。
(3)RunLoop
本身的超时工夫到了。
(4)被其余调用者手动唤醒。
8、告诉观察者线程将被唤醒。
9、解决唤醒时收到的事件。
(1)如果用户定义的定时器启动,解决定时器事件并重启 RunLoop。进入步骤 2。
(2)如果输出源启动,传递相应的音讯。
(3)如果 RunLoop 被显示唤醒而且工夫还没超时,重启 RunLoop。进入步骤 2
10、告诉观察者 RunLoop
完结。
7. 怎么创立一个常驻线程?
1、为以后线程开启一个 RunLoop
(第一次调用 [NSRunLoop currentRunLoop]
办法时
理论是会先去创立一个 RunLoop
)
2、向以后 RunLoop
中增加一个 Port/Source
等维持 RunLoop
的事件循环(如果 RunLoop
的 mode
中一个 item
都没有,RunLoop
会退出)
3、启动该 RunLoop
8.RunLoop 的数据结构
NSRunLoop(Foundation)
是 CFRunLoop(CoreFoundation)
的封装,提供了面向对象的API
RunLoop
相干的次要波及五个类:
CFRunLoop
:RunLoop
对象
CFRunLoopMode
:运行模式
CFRunLoopSource
:输出源 / 事件源
CFRunLoopTimer
:定时源
CFRunLoopObserver
:观察者
1、CFRunLoop
由 pthread
(线程对象,阐明 RunLoop
和线程是一一对应的)、currentMode
(以后所处的运行模式)、modes (多个运行模式的汇合)、commonModes
(模式名称字符串汇合)、
commonModelItems
(Observer
,Timer
,Source
汇合)形成
2、CFRunLoopMode
由 name
source0
source1
observers
timers
形成
3、CFRunLoopSource
分为 source0
和 source1
两种
source0:
即非基于 port
的,也就是用户触发的事件。须要手动唤醒线程,将以后线程从内核态切换到用户态
source1:
基于 port
的,蕴含一个 mach_port
和一个回调,可监听系统端口和通过内核和其余线程发送的音讯,能被动唤醒RunLoop
,接管散发零碎事件。具备唤醒线程的能力
4、CFRunLoopTimer
基于工夫的触发器,基本上说的就是 NSTimer
。在预设的工夫点唤醒 RunLoop
执行回调。因为它是基于 RunLoop
的,因而它不是实时的(就是 NSTimer
是不精确的。因为 RunLoop
只负责分发祥的音讯。如果线程以后正在解决沉重的工作,就有可能导致 Timer 本次延时,或者少执行一次)。
5、CFRunLoopObserver
监听以下工夫点: CFRunLoopActivity
kCFRunLoopEntry : RunLoop
筹备启动
kCFRunLoopBeforeTimers
:RunLoop
将要解决一些 Timer
相干事件
kCFRunLoopBeforeSources
:RunLoop
将要解决一些 Source
事件
kCFRunLoopBeforeWaiting
:RunLoop
将要进行休眠状态, 行将由用户态切换到内核态
kCFRunLoopAfterWaiting
:RunLoop
被唤醒,即从内核态切换到用户态后
kCFRunLoopExit
: RunLoop
退出
kCFRunLoopAllActivities
:监听所有状态
6、各数据结构之间的分割
线程和 RunLoop
一一对应, RunLoop
和 Mode
是一对多的,Mode
和 source
、timer
、observer
也是一对多的
9. 解释一下 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
10. 解释一下事件响应的过程?
苹果注册了一个 Source1
(基于 mach port
的) 用来接管零碎事件,其回调函数为_IOHIDEventSystemClientQueueCallback
()。
当一个硬件事件 (触摸 / 锁屏 / 摇摆等) 产生后,首先由 IOKit.framework
生成一个 IOHIDEvent
事件并由 SpringBoard
接管。这个过程的详细情况能够参考这里。
SpringBoard
只接管按键 (锁屏 / 静音等),触摸,减速,靠近传感器等几种Event
,随后用 mach port
转发给须要的 App
过程。随后苹果注册的那个 Source1
就会触发回调,并调用_UIApplicationHandleEventQueue
() 进行利用外部的散发。
_UIApplicationHandleEventQueue
() 会把 IOHIDEvent
解决并包装成 UIEvent
进行解决或散发,其中包含辨认 UIGesture
/ / UIWindow
等。通常事件比方 UIButton
点击、touchesBegin
/Move
/End
/Cancel
事件都是在这个回调中实现的。
11. 解释一下手势辨认的过程?
当下面的 _UIApplicationHandleEventQueue
()辨认了一个手势时,其首先会调用 Cancel
将以后的 touchesBegin
/Move
/End
系列回调打断。随后零碎将对应的 UIGestureRecognizer
标记为待处理。
苹果注册了一个 Observer
监测 BeforeWaiting
(Loop
行将进入休眠) 事件,这个
Observer
的回调函数是 _UIGestureRecognizerUpdateObserver
(),其外部会获取所有刚被标记为待处理的 GestureRecognizer
,并执行 GestureRecognizer
的回调。
当有 UIGestureRecognizer
的变动 (创立 / 销毁 / 状态扭转) 时,这个回调都会进行相应解决。
12. 利用 runloop 解释一下页面的渲染的过程?
当咱们调用 [UIView setNeedsDisplay]
时,这时会调用以后 View.layer
的 [view.layer setNeedsDisplay]
办法。
这等于给以后的 layer
打上了一个脏标记,而此时并没有间接进行绘制工作。而是会到以后的 Runloop
行将休眠,也就是 beforeWaiting
时才会进行绘制工作。
紧接着会调用 [CALayer display]
,进入到真正绘制的工作。CALayer
层会判断本人的 delegate
有没有实现异步绘制的代理办法 displayer
:,这个代理办法是异步绘制的入口,如果没有实现这个办法,那么会持续进行零碎绘制的流程,而后绘制完结。
CALayer
外部会创立一个Backing Store
,用来获取图形上下文。接下来会判断这个layer
是否有delegate
。
如果有的话,会调用 [layer.delegate drawLayer:inContext:]
,并且会返回给咱们 [UIView DrawRect:]
的回调,让咱们在零碎绘制的根底之上再做一些事件。
如果没有 delegate
,那么会调用 [CALayer drawInContext:]
。
以上两个分支,最终 CALayer
都会将位图提交到 Backing Store
,最初提交给 GPU。至此绘制的过程完结。
13. 什么是异步绘制?
异步绘制,就是能够在子线程把须要绘制的图形,提前在子线程解决好。将筹备好的图像数据间接返给主线程应用,这样能够升高主线程的压力。
异步绘制的过程
要通过零碎的 [view.delegate displayLayer:]
这个入口来实现异步绘制。
代理负责生成对应的 Bitmap
设置该 Bitmap 为 layer.contents
属性的值
以上就是本次分享,感激观看!