Runloop

作者: 飞哥漂流记 | 来源:发表于2020-05-27 14:55 被阅读0次

    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

          本文链接:https://www.haomeiwen.com/subject/zmokahtx.html