参考
RunLoop
是 iOS 和 OSX 开发中的概念,首先,先简单介绍我目前已知的东西
RunLoop和线程的关系:
RunLoop
的作用就是用来管理线程的,当线程的RunLoop
开启后,线程就会在执行完任务后,处于休眠状态,随时等待接受新的任务,而不是退出。- 只有主线程的
RunLoop
是默认开启的,所以程序在开启后,会一直执行,不会退出,其他线程的RunLoop
如果需要开启,就手动开启。
RunLoop内部是如何实现的:
- 有一个判断循环的条件,满足条件,就一直循环。
- 线程得到唤醒事件被唤醒,事件处理完毕以后,回到睡眠状态,等待下次唤醒。
上面的谈论可能太笼统了,下面我们来详细谈谈RunLoop的那些事儿:
RunLoop的概念:
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。所以我们需要这样一个机制,让线程能随时处理事件。
首先类似这样的模型,一般被称为 Event Loop
,这样的模型的关键在于:如何管理事件和消息,如何让线程在没有处理消息时休眠以避免资源占用,在有消息到来时立刻被唤醒。
所以,实际上,RunLoop
是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个函数来执行 Event Loop
的逻辑。
线程执行了 Event Loop
这个函数后,就会一直处于这个函数内部接受消息->等待->处理
的循环中,直到这个循环结束(例如quit
),函数返回。
OSX/iOS
中,提供了两个这样的对象:NSRunLoop
和CFRunLoopRef
。CFRunLoopRef
是在CoreFoundation
框架内的,它提供了纯c的API
,且都是线程安全的NSRunLoop
是基于CFRunLoopRef
的封装,提供了面向对象的API
,但是这些API
不是线程安全的
tips
- 关于线程安全:简单理解为一个方法或者一个实例可以在多线程环境中使用 而不会出现问题
- 产生线程不安全的原因:在我理解下,这种情况大约是一或多个线程向相同的资源做了写操作才能发生,只要资源本身没有变化,那么多个线程读取相同资源应该是安全的(关于这一点,还需要查资料)
苹果不允许直接创建RunLoop,它提供了两个自动获取的函数CFRunLoopGetMain()和CFRunLoopGetCurrent(), 线程和RunLoop之间是一一对应的,其关系是保存在一个全局的Dictionary里。
线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生在线程结束时。你只能在一个线程的内部获取其RunLoop(主线程除外)
(RunLoop在NSRunLoop的实践,还需要自己去编码感受一下,source,timer,mode,Observer)
RunLoop的底层实现:
RunLoop
的核心是基于 mach port
的,其进入休眠时调用的函数是 mach_msg()
OSX/iOS
的系统架构基本上分为:应用层(SpringBoard
等),应用框架层(Cocoa
等框架),核心框架层(核心框架,openGL
等),Darwin
(操作系统核心,包括内核,驱动等)
Darwin这个核心的架构:
在硬件层上面的三个组成部分:Mach
、BSD
、IOKit
(还有其他内容),共同组成了XNU 内核.
XNU 内核的内环被称作 Mach
,其作为一个微内核,仅提供了诸如处理器调度、IPC
(进程间通信)等非常少量的基础服务。
BSD
层可以看作围绕 Mach
层的一个外环,其提供了诸如进程管理、文件系统和网络等功能。
IOKit
层是为设备驱动提供了一个面向对象(C++)的一个框架。
Mach
本身提供的 API 非常有限,而且苹果也不鼓励使用 Mach
的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在 Mach
中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。和其他架构不同, Mach
的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。”消息”是 Mach
中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach
的 IPC
(进程间通信) 的核心。
一条 Mach
消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口 local_port
和目标端口 remote_port
,发送和接受消息是通过同一个 API 进行的,其 option
标记了消息传递的方向。
为了实现消息的发送和接收,mach_msg()
函数实际上是调用了一个 Mach
陷阱 (trap),即函数mach_msg_trap()
,陷阱这个概念在 Mach
中等同于系统调用。当你在用户态调用 mach_msg_trap()
时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg()
函数会完成实际的工作。
总结:
RunLoop
的核心就是一个mach_msg()
,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在mach_msg_trap()
这个地方。