关于ios:iOS开发面试只需知道这些技术基本通关RunLoop篇

6次阅读

共计 5886 个字符,预计需要花费 15 分钟才能阅读完成。

1. 为什么 NSTimer 有时候不好使?

因为创立的 NSTimer默认是被退出到了 defaultMode,所以当 RunloopMode 

化时,以后的 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() 来开释主动开释

池。这个 Observerorder是 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。次要是为了分隔开不同的 SourceTimerObserver,让它们之间互不影响。

RunLoop 运行在 Mode1 上时,是无奈承受解决 Mode2Mode3 上的 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() 函数会实现理论的工作。

即基于 portsource1,监听端口,端口有音讯就会触发回调;而 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

分为 source0source1 两种

source0:

即非基于 port 的,也就是用户触发的事件。须要手动唤醒线程,将以后线程从内核态切换到用户态

source1:

基于 port 的,蕴含一个 mach_port 和一个回调,可监听系统端口和通过内核和其余线程发送的音讯,能被动唤醒RunLoop,接管散发零碎事件。具备唤醒线程的能力

4、CFRunLoopTimer

基于工夫的触发器,基本上说的就是 NSTimer。在预设的工夫点唤醒 RunLoop 执行回调。因为它是基于 RunLoop的,因而它不是实时的(就是 NSTimer 是不精确的。因为 RunLoop只负责分发祥的音讯。如果线程以后正在解决沉重的工作,就有可能导致 Timer 本次延时,或者少执行一次)。

5、CFRunLoopObserver

监听以下工夫点: CFRunLoopActivity

kCFRunLoopEntry : RunLoop筹备启动

kCFRunLoopBeforeTimersRunLoop将要解决一些 Timer 相干事件

kCFRunLoopBeforeSourcesRunLoop将要解决一些 Source 事件

kCFRunLoopBeforeWaitingRunLoop将要进行休眠状态, 行将由用户态切换到内核态

kCFRunLoopAfterWaiting RunLoop被唤醒,即从内核态切换到用户态后

kCFRunLoopExit  RunLoop退出

kCFRunLoopAllActivities:监听所有状态

6、各数据结构之间的分割

线程和 RunLoop 一一对应, RunLoop Mode 是一对多的,Modesourcetimerobserver 也是一对多的

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  属性的值

以上就是本次分享,感激观看!

正文完
 0