Runloop

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

5. 猜想runloop内部是如何实现的?

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模式下,等手势停止滑动后,才进行更新,这样就不打断用户的滑动操作

你可能感兴趣的:(Runloop)