您的位置:首页 > 移动开发 > IOS开发

iOS多线程编程(一)——RunLoop基本概念

2016-06-06 23:26 435 查看
之前一直知道iOS中RunLoop的概念,但是对于如何使用以及其深入的概念并无任何了解。之前对于iOS多线程的了解,也仅仅限于GCD的概念及使用,对于之前的NSThread,RunLoop也没有做过任何了解。但是既然他们都属于iOS中的多线程编程的范畴,那么不妨就一起学习终结一下。

先从RunLoop开始。

What is RunLoop?

RunLoop属于iOS线程编程的范畴,虽然自身并不涉及线程的创建以及管理,但是却是一个与线程相关的提高程序并发效率的基础构件。

根据Apple官方文档,RunLoop的定义为:

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.


总结为

1. RunLoop可以处理投递给他的事件源。

2. RunLoop提供了一套可以协调这些事件源的机制。

3. 当有事件处理时,RunLoop使Thread保持忙碌,当空闲时,RunLoop会让Thread进入sleep状态。

理解RunLoop,也就可以从事件源、协调机制、忙碌\Sleep状态切换 来入手。

RunLoop如同其名字,它是一个循环,通过运行循环内的event handlers来处理incoming events。

incoming events, 可以分为两种

(1)Input sources

Input sources 向RunLoop投递异步事件(投递者投了就走, 不会等待事件处理完毕)。这种事件经常由其他线程或其他程序投递而来。

(2)Timer sources

Timer sources会在某个时间点触发或规律的重复触发,Timer sources是同步的。



上图中显示了Input source和Timer sources向RunLoop投递events的情况。

Input sources 异步地向RunLoop投递事件到对应的handler,同时使runUntilDate函数退出(想继续监听Input sources,需要再次调用runUntilDate)。

Timer sources同步地将事件投递到对应的handler,但不会使runUntilDate退出(在同一个线程上不退出又如何调用handler?)。

与此同时,系统会对RunLoop处于不同状态时进行通知,用户可以注册runloop observers来接收这些消息。

那么,对于RunLoop的理解,可以扩展为

事件源
协调机制
RunLoop Observer
忙碌\Sleep状态切换

对于RunLoop,还有两点需要补充:

RunLoop,你并不需要去创建它,每一个线程,都有一个对应的RunLoop对象。

但是,对于子线程来说,虽然其拥有RunLoop对象,你还需要手动的让它运行起来(调用run方法)。

对于app的main thread来说,系统在程序启动时 会自动启动main thread对应的RunLoop,这个RunLoop,就可以作为app的用户事件处理等的基础设施。

RunLoop并不是完全自动化的,你需要

1.在适当的时机启动run loop

2.提供对应的handler来处理incoming event。

RunLoop Mode

每当RunLoop运行时,总会为其制定一个运行Mode,只有符合当前Mode的Input sources,Timer sources 和 Run Loop observers 才能够工作。

Mode种类

在代码中,我们可以定义自己的RunLoop Mode,仅仅提供一个名字即可。虽然自定义mode很简单,但是为了确保mode工作,你必须为你的mode提供至少一个Input sources,timers 或者run-loop observer。

而Cocoa和Core Foundation 已经定义了一个default mode和一些通用mode。

下表中,列出了iOS系统定义的Mode



RunLoop事件源

RunLoop的事件源分为两种Input Sources 与 Timer Sources,对于他们的不同,我们在上面做了简单的介绍。

对于RunLoop的事件源,同时要指定事件源的匹配的RunLoop Mode(可指定匹配多个Mode)。

 RunLoop每次仅会处于一个Mode中,同时RunLoop只会接收与当前Mode相匹配的事件源投递的事件,对于那些Mode不匹配的事件源,则会被挂起,直到RunLoop转入对应的Mode中。

另外需要注意的是,如果想让事件源投递过来的事件得到处理,则必须确保RunLoop run起来。在iOS中,只有你自己创建的Thread才需要手动的run,而对于系统线程,如Main thread,它的RunLoop会被系统自动启动。

下面我们在具体学习一下Input Sources。

Input Sources

Input Sources实现的events的异步投递。

input Sources分为两种:

(1)Port-based input sources

   用于监视程序对应的Mach port。

(2)Custom input sources 

   用于监视自定义事件源。

Port-based input sources 与 Custom input sources唯一的区别在于,Port-based input sources 由系统kernel触发,而Custom input sources必须人为地在其他线程触发。

Cocoa Perform Selector Sources

Cocoa 定义了一种custom input source,我们称之为Perform Selector Sources。

与port-based source相同,perform selector source在目标线程上被顺序执行。

而与port-based source不同的是,perform selector source在执行完对应的selector后,自动将其自身移除run loop。

在每次的RunLoop循环中,所有当前的perform selector都会得到处理,而不是每次循环仅执行一个perform selector。



Timer Sources

Timer sources 是不同于Input sources的另一种事件源。

Timer sources会串行地在某个时间点向目标线程投递事件。

要保证Timer事件被触发,需要满足以下条件:

(1)RunLoop必须在Timer对应的Mode下运行,否则Timer不会触发。

(2)RunLoop必须run起来,否则Timer不会触发。

(3)对于scheduled timer,如果timer被延迟触发,导致跳过了许多scheduled 间隔(如RunLoop Mode不匹配而被挂起的情况),那么这些被跳过的timer只会执行一次,然后系统重新计算下一个schedule时间点。

这里有一个关于timer常见的场景。

例如我们在做一款考试APP,题目通过scroll view来左右切换,同时在navigation bar上通过Timer来实现考试的倒计时功能。

那么当我们用手指快速的滑动scroll view来切换题目时,会发现倒计时时钟静止。

这是因为当我们在滑动scroll view时,Main thread 的Runloop会切换到Event Tracking mode来跟踪用户的滑动操作,而如果我们没有特别指定timer的mode,则其默认mode为default mode。Mode不匹配,导致RunLoop上的timer时间不会被处理。

为了解决这个问题,我们可以指定Timer 的mode:

[[NSRunLoop currentRunLoop] addTimer:_countDownTimer forMode:NSRunLoopCommonModes];

将Timer加入到CommonModes下(它其实是一个mode集合,在Cocoa下,包含default,event tracking,在Core Foundation下,仅默认包含default mode)。

Run Loop Observers

run loop observers能够监听run loop的各种状态:

进入RunLoop
当RunLoop将要处理一个timer事件 
当RunLoop将要处理一个input source
当RunLoop即将进入sleep状态
当RunLoop被唤起但还未处理将其唤醒的事件时
当RunLoop退出时。

同timers一样,observer分为一次性触发和重复触发,当一次性触发observer触发后,其会从runloop中移除,而多次触发的observer则会继续留在runloop 中。

RunLoop事件序列

RunLoop本质上是一个循环,当没有事件要处理时,RunLoop进入sleep状态来节省CPU时间,而当有事件要处理时,RunLoop则会按照如下步骤,对事件进行处理

通知observer run loop被触发
如果有timers事件的话,通知observer timer event要被处理
如果有非 port-based input source要处理的话,通知observer
触发所有的准备完毕的非port-based input source(一次RunLoop循环会处理所有的非port-based input sources 的event)
如果有port-based input source要被触发,则处理该事件,然后转到9(一次RunLoop循环仅处理一个port-based input source)
通知Observer runloop将进入sleep状态
使RunLoop所在线程Sleep,直到有下面情况之一发生

在port-based input source接收到新的event
timers fires
设定的runloop超时时间已到
runloop被明确地唤醒
8. 通知observer runloop被woke up

9.处理待解决的event

如果用户定义的timer被触发,处理timer event并重启RunLoop,跳到2
如果一个input source被触发,派发event
如果runloop是被唤醒同时设置的超时时间未到,转至2

10. 通知observer runloop将要exited。

参考文章

http://www.jianshu.com/p/37ab0397fec7

http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios runloop