前言:
这篇文章主要内容是介绍RunLoop的一些概念以及用法:使用RunLoop创建常驻线程、自定义输入源进行线程通信等。同时借此机会希望能够和大家一起讨论RunLoop相关的知识,加深对RunLoop的理解。
一、RunLoop是什么?
RunLoop
是与线程相关的基础架构中的一部分,它是一个处理事件的循环(线程进入这个循环,运行事件处理程序来响应传入的事件),RunLoop
的目的是当有事件需要处理时,线程是活跃的、忙碌的,当没有事件后,线程进入休眠。
RunLoop结构以及事件来源:
一个RunLoop
包含若干个Mode
,每个Mode
包含若干个Source
/Timer
/Observer
/Port
。当启动一个RunLoop
时会先指定一个Mode
,检查指定Mode
是否存在以及Mode
中是否含有Source
和Timer
,如果Mode
不存在或者Mode
中无Source
和Timer
,认为该Mode
是一个空的Mode
,RunLoop
就直接退出。
Input Source:
Port-Based Sources:监听
App
的Mach Port
,由内核发出信号,输入源收到信号后,执行相关的例程。Custom Input Sources:监听自定义的输入源,需要在其它线程手动发送信号,输入源收到信号后,执行相关的例程。
Cocoa Perform Selector Sources:
Cocoa
中自定义的输入源,目的是在不同线程中执行任务,同一线程中的任务是顺序执行的,当任务执行完成后系统会自动移除这个源。(注意:在目标线程中执行任务时,这个目标线程必须有活跃的RunLoop)
Timer Source:
时间源会在预设的时间同步传递事件给对应的线程,计时器是线程通知自己做某事的一种方式。
计时器并不是真正的实时的,当计时器未处于RunLoop
当前监听的Mode
,那么计时器是不会计时调度任务的,只有RunLoop
当前监听的Mode
是计时器关联的Mode
时,计时器才会开始执行任务,例如:NSTimer
添加至主线程RunLoop
的DefaultMode
中,此时滑动TableView
/ScrollView
时,RunLoop
会切换至TrackMode
,计时器是不会调度任务的。
如果RunLoop
在执行一个例程时,计时器触发了,那么计时器会等待RunLoop
将该例程执行完成,在下一次的循环中处理。在RunLoop
未运行情况下,计时器永远不会触发任务。
二、RunLoop怎么使用?
应用启动时,会自动在主线程上设置运行RunLoop
,所以不需要在主线程上显示的启动RunLoop
,无需调用[[NSRunLoop currentRunLoop] runUntilDate:]
这些方法。那么如果我们显示的在主线程中调用RunLoop
的run
方法会出现什么结果呢?通过Demo
中显示,主线程中显示启动RunLoop
会影响当前事件处理,但是由于RunLoop
并没有停止,所以其他事件能够正常接收和处理。
而子线程也不并是必须要设置运行RunLoop
才能执行任务,比如说只是简单在子线程中处理个耗时任务等,如下场景是需要启动RunLoop
的:
- 使用
NSPort
或者自定义输入源与其它线程通信。 - 在线程上使用计时器。
- 在一个
Cocoa
应用中使用performSelector
相关方法。 - 使线程常驻,在该线程定期执行任务。
正如前言中所说,本文主要说明线程常驻和自定义输入源线程通信。
线程常驻:
方式一:无条件的启动RunLoop
是最简单的选择,但它也是最不可取的选择,它会将线程置于永久循环中,这样几乎无法控制RunLoop
本身,虽然可以添加和删除输入源和计时器,但停止RunLoop
的唯一方法是杀死RunLoop
。(以上内容是通过Google翻译的官网内容可能理解有些偏差届时还望指正,事实上我在做实验的过程中,发现使用NSThread
的cancel
方法是无法停止RunLoop
的,cancel
方法是更改线程的取消状态,指示它应该退出。在当前线程下执行[NSThread exit]
方法,退出了该线程,但demo
中的LongLifeThreadViewController
仍然未被释放)
方式二:启动RunLoop
时设定时限,RunLoop
将一直运行直到事件到达或分配的时间到期。如果事件到达,则将该事件分派给处理程序进行处理,然后退出此次RunLoop
。可以通过重新启动RunLoop
处理下一个事件。同样如果分配的时间到期,也可以重新启动RunLoop
来处理。这种方式可以指定RunLoopMode
,官网力荐。
自定义输入源线程通信:
定义输入源:
- 提供输入源要处理的信息。
- 接收到事件时的执行例程。
- 输入源加到
RunLoop
时的执行例程。 - 输入源失效时的执行例程。
个人感觉可以根据个人需求决定是否实现第3、4两条内容。(注意定义输入源只能通过CoreFoundation提供的对应API实现,其中的回调例程由C语言实现)
在RunLoop
上安装输入源:如果实现了上述的第3条内容时,将自定义的输入源添加到RunLoop
时,就会回调输入源对应的schedule
实现例程。
向输入源发送信号:输入源在接收到信号后,会执行对应的perform
例程,perform
例程就是对应事件处理程序。(注意如果线程处于休眠状态,要唤醒线程,否则该事件无法被处理。
结语:
关于RunLoop
的内容还有很多,比如:RunLoopModes
、RunLoopObserver
、NSPort
、NSTimer
等等,当然还有RunLoop
的源码,这些内容在此并未列出,如有感兴趣的小伙伴可以先行花时间去探索、学习,到时可以一起交流、讨论。
源码地址:QiRunLoopDemo1
推荐文章:
iOS 常用调试方法:LLDB命令
iOS 常用调试方法:断点
iOS 常用调试方法:静态分析
iOS消息转发
iOS 自定义拖拽式控件:QiDragView
iOS 自定义卡片式控件:QiCardView
iOS Wireshark抓包
iOS Charles抓包