Runloop是通过内部维护的事件循环来对事件或者消息进行管理的一个对象
没有消息需要处理时,休眠以避免资源占用
用户态 —— 内核态
有消息处理时,立刻被唤醒
内核态 —— 用户态
用户态:用户层面
内核态:系统调用
唤醒RunLoop的方式: source1 Timer事件 外部手动唤醒
在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。
RunLoop的主要目的:保证程序执行的线程不会被系统终止。什么时候使用Runloop ? 当需要和该线程进行交互的时候才会使用Runloop.
结构:
NSRunLoop是对CFRunLoop的封装,提供了面向对象的API
CFRunLoop :{
包含pthread : 一一对应(RunLoop和线程的关系)
currentMode : CFRunLoopMode
modes :NSMutableSet< CFRunLoopMode >
commonModes : NSMutableSet< String *>
commonModeItems : Source/Timer/Observer
}
CFRunLoopMode {
name: 例如 NSDefaultRunLoopMode
sources0: NSMutableSet 需要手动唤醒线程
sources1: NSMutableSet 具备唤醒线程的能力
observer: 数组
timers: 数组
}
一个RunLoop包含若干个Mode,每个Mode包含若干个Source/Timer/Observer/Port。当启动一个RunLoop时会先指定一个Mode,检查指定Mode是否存在以及Mode中是否含有Source和Timer,如果Mode不存在或者Mode中无Source和Timer,认为该Mode是一个空的Mode,RunLoop就直接退出
苹果文档中提到的 Mode 有五个,分别是:
NSDefaultRunLoopMode
NSConnectionReplyMode
NSModalPanelRunLoopMode
NSEventTrackingRunLoopMode
NSRunLoopCommonModes : 不是实际存在的一种mode,是同步Source/Timer/Observer到多个Mode的一个技术方案
Source/Timer/Observer
CFRunLoopObserver:观测RunLoop的时间点{
CFRunLoopEntry
CFRunLoopBeforeTimes
CFRunLoopBeforeSources
CFRunLoopBeforeWaiting
CFRunLoopAfterWaiting
CFRunLoopExit
}
Runloop 与线程
RunLoop 和线程是息息相关的,我们知道线程的作用是用来执行特定的一个或多个任务,在默认情况下,线程执行完之后就会退出,就不能再执行任务了。这时我们就需要采用一种方式来让线程能够不断地处理任务,并不退出。所以,我们就有了 RunLoop。
一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的 RunLoop 对象。
RunLoop 并不保证线程安全。我们只能在当前线程内部操作当前线程的 RunLoop 对象,而不能在当前线程内部去操作其他线程的 RunLoop 对象方法。
RunLoop 对象在第一次获取 RunLoop 时创建,销毁则是在线程结束的时候。
主线程的 RunLoop 对象系统自动帮助我们创建好了(原理如 1.3 所示),而子线程的 RunLoop对象需要我们主动创建和维护
原理(实现机制):
通知观察者RunLoop已经启动
通知观察者即将要开始的定时器
通知观察者任何即将启动的非基于端口的源
启动任何准备好的非基于端口的源
如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9
通知观察者线程进入休眠状态
将线程置于休眠知道任一下面的事件发生:
某一事件到达基于端口的源
定时器启动
RunLoop设置的时间已经超时
RunLoop被显示唤醒
通知观察者线程将被唤醒
处理未处理的事件
如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2
如果输入源启动,传递相应的消息
如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
通知观察者RunLoop结束。
RunLoop实战应用:
1 NSTimer的使用
CFRunLoopAddTimer( , , )
2 ImageView推迟显示
有时候,我们会遇到这种情况:
当界面中含有UITableView,而且每个UITableViewCell里边都有图片。这时候当我们滚动UITableView的时候,如果有一堆的图片需要显示,那么可能会出现卡顿的现象。
怎么解决这个问题呢?
这时候,我们应该推迟图片的显示,也就是ImageView推迟显示图片。有两种方法:
监听UIScrollView的滚动
因为UITableView继承自UIScrollView,所以我们可以通过监听UIScrollView的滚动,实现UIScrollView相关delegate即可
利用PerformSelector设置当前线程的RunLoop的运行模式
利用performSelector方法为UIImageView调用setImage:方法,并利用inModes将其设置为RunLoop下NSDefaultRunLoopMode运行模式。代码如下:
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"tupian"] afterDelay:4.0 inModes:NSDefaultRunLoopMode];
3 后台常驻线程(很常用)
我们在开发应用程序的过程中,如果后台操作特别频繁,经常会在子线程做一些耗时操作(下载文件、后台播放音乐等),我们最好能让这条线程永远常驻内存。
面试题:
1. runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
Runloop是事件接收和分发机制的一个实现,Runloop提供了一种异步执行代码的机制,不能并行执行任务。
在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。
RunLoop的主要目的:保证程序执行的线程不会被系统终止。
当需要和该线程进行交互的时候才会使用Runloop.
每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。
一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。
RunLoop,正如其名所示,是线程进入和被线程用来相应事件以及调用事件处理函数的地方.需要在代码中使用控制语句实现RunLoop的循环,也就是说,需要代码提供while或者for循环来驱动RunLoop.
在这个循环中,使用一个runLoop对象[NSRunloop currentRunloop]执行接收消息,调用对应的处理函数.
Runloop接收两种源事件:input sources和timer sources。
(输入源有3种类型)Selector源:如例子按钮事件中的performSelector,当在子线程中执行Selector时,目标线程必须RunLoop处于开启状态,不然Selector就一直处于休眠状态;
基于端口的输入源:就是之前提到的Source1。通过内置的端口相关的对象和函数,创建配置基于端口的输入源。 例如可以使用NSPort的方法把该端口添加到 RunLoop;
自定义输入源:创建custom输入源,必须使用Core Foundation里面的CFRunLoopSourceRef类型相关的函数来创建,并自定义自己的行为和消息传递机制;
Runloop工作的特点:
1>当有时间发生时,Runloop会根据具体的事件类型通知应用程序作出相应;
2>当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的;
3>当事件再次发生时,Runloop会被重新唤醒,处理事件.
2.runloop的mode是用来做什么的?有几种mode?
CFRunLoopModeRef有5种形式:
kCFRunLoopDefaultMode
默认模式,通常主线程在这个模式下运行
UITrackingRunLoopMode
界面跟踪Mode,用于追踪Scrollview触摸滑动时的状态。
kCFRunLoopCommonModes
占位符,带有Common标记的字符串,比较特殊的一个mode;
UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
GSEventReceiveRunLoop:内部Mode,接收系事件
3.为什么把NSTimer对象以NSDefaultRunLoopMode添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
nstime对象是在NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes
4.苹果是如何实现Autorelease Pool的?
Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器-1
6.主线程中的RunLoop什么时候开始?什么时候释放?
7. RunLoop 的实现原理和数据结构,什么时候会用到?
8. app如何接收到触摸事件的?
9. 为什么只有主线程的runloop是开启的?
10. 为什么只在主线程刷新UI?
像UIKit这样大的框架上确保线程安全是一个重大的任务,会带来巨大的成本。UIKit不是线程安全的,假如在两个线程中设置了同一张背景图片,很有可能就会由于背景图片被释放两次,使得程序崩溃。或者某一个线程中遍历找寻某个subView,然而在另一个线程中删除了该subView,那么就会造成错乱。apple有对大部分的绘图方法和诸如UIColor等类改写成线程安全可用,可还是建议将UI操作保证在主线程中
主线程上默认是开始 runloop 的,子线程没有 runloop 也无法监听一些事件,手势刷新UI等操作
在子线程更新UI可能会无效,也可能会崩溃
11. PerformSelector和runloop的关系?
12. 如何使线程保活?
为当前线程开启一个RunLoop
向该RunLoop中添加一个port/source等维持RunLoop的事件循环
启动该RunLoop
13. 以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
14. 介绍 runloop 相关的知识和在实际开发中的使用情况?
RunLoop顾名思义,是运行循环。它跟线程是一一对应的,每一个线程都有一个RunLoop,在需要的时候创建。RunLoop的作用很简单,就是保持线程不会退出,并且处理一些事件
UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这样就达到了可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
对其它线程来说,runloop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
在任何一个Cocoa程序的线程中,都可以通过以下代码来获取到当前线程的runloop
NSRunLoop*runloop=[NSRunLoop currentRunLoop];
Runloop 通过model 来指定事件在运行循环中的优先级的。
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
实际应用中,可以利用Runloop完成一些耗时操作,比如tableview加载图片,还可以用来让定时器不随着屏幕滚动等操作停止等等。
https://blog.ibireme.com/2015/05/18/runloop/
15. 怎么保证子线程数据回来更新UI的时候不打断用户的滑动操作?
在用户进行滑动操作的时候,当前的RunLoop运行在NSEventTrackingRunLoopMode下面,而我们一般的网络的请求是放在子线程中,我们可以把子线程中更新UI的操作包装一下 把它放在NSDefaultRunLoopMode模式下,等手势停止滑动后,才进行更新,这样就不打断用户的滑动操作
网友评论