iOS开发面试拿offer攻略之RunLoop篇

<meta charset="utf-8">

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

因为创建的 NSTimer默认是被加入到了 defaultMode,所以当 RunloopMode

化时,当前的 NSTimer 就不会工作了。

2.AFNetworking 中如何运用 Runloop?

RunLoop 启动前内部必须要有至少一个Timer/Observer/Source ,所以AFNetworking[runLoop run] 之前先创建了一个新的NSMachPort 添加进去了。

通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个

port 发送消息到loop 内;但此处添加port 只是为了让RunLoop 不至于退出,并没有用于实际的发送消息。

image
image

当需要这个后台线程执行任务时, AFNetworking通过调用

[NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

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

关于 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)一个基于 portSource1的事件(图里应该是 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的事件循环(如果 RunLoopmode 中一个item 都没有,RunLoop 会退出)

3、启动该 RunLoop

8.RunLoop的数据结构

NSRunLoop(Foundation)CFRunLoop(CoreFoundation) 的封装,提供了面向对象的API

RunLoop 相关的主要涉及五个类:

CFRunLoopRunLoop 对象

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将要进行休眠状态,即将由用户态切换到内核态

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

kCFRunLoopExitRunLoop退出

kCFRunLoopAllActivities : 监听所有状态

6、各数据结构之间的联系

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

9.解释一下 NSTimer

NSTimer 其实就是 CFRunLoopTimerRef,他们之间是toll-free bridged 的。一个NSTimer 注册到RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如

10:00, 10:10, 10:20 这几个时间点。RunLoop 为了节省资源,并不会在非常准确的时间点回调这个 TimerTimer有个属性叫做 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属性的值

以上就是本次分享,感谢观看!
以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容