深入理解runloop

线程中的runloop

在开发中,我们会经常接触到线程,比如在主线程中更新UI,在子线程中异步请求等,而线程中最重要的一个组成部分便是runloop,其是用来管理线程的。runloop在线程中有以下两个作用:

  1. 保证线程不退出
  2. 监听并处理事件,使得线程有事件时工作,无事件时睡眠

每个线程中都有一个runloop,主线程中的runloop是在应用程序启动时创建的,默认是开启的,子线程中的runloop采用的是延迟加载的方式,如果我们不主动获取,子线程的runloop是不会创建的。

解析runloop

runloop三要素: Modes(模式)、sources(源)、Observers(观察者)。当有事件源触发时,runloop会被唤醒并处理事件源的方法,并通知该runloop运行模式下的所有观察者。

Run Loop Modes(运行循环模式)

运行循环在工作时,会有多种运行模式。我们常用到的模式有:NSDefaultRunLoopMode和NSRunLoopCommonModes 。每个RunLoop Mode都可以看作是一个集合,其中包含了其监听的事件源以及在事件发生时需要通知的RunLoop Observers。在相应的模式下,只有对应于该模式的source会被处理,同样也只有对应于该模式的observer会被通知。其实运行循环模式起到一个过滤作用,可以过滤到我们不关心的其他的source事件。

Cocoa 和 Core Foundation框架定义了一个默认的和一些常用的运行循环模式,如下表:

Mode Name Description
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 默认模式,一般设置为此模式.
Connection NSConnectionReplyMode (Cocoa) Cocoa用该模式来监听NSConnection请求的回复,该模式为系统使用,我们一般不会用到
Modal NSModalPanelRunLoopMode (Cocoa) Cocoa 用此模式来区分 modal panels事件.
Event tracking NSEventTrackingRunLoopMode (Cocoa) Cocoa 在该模式下,限制其他source的事件
Common modes NSRunLoopCommonModes (Cocoa)kCFRunLoopCommonModes (Core Foundation) 这是一个可配置的模式组,在该模式下的source 事件,和模式组里的其他模式都会产生联系。在Cocoa框架中,该模式默认包含Default、Modal和 Event tracking 三种模式,在Core Foundation中默认只包含Default模式,我们可以用CFRunLoopAddCommonMode 函数来添加自定义的模式。
Run Loop Sources

先来看runloop和sources的结构图

runloop.png

runloop可以监听并处理事件,其监听的事件源有两种类型:Input sources(输入源) 和** Timer sources(定时器源)**。输入源异步传递事件到线程中,通常消息来自其它线程或不同的应用,定时器源同步传递事件。

  • Input sources
    输入源异步发送事件到线程中,输入源共有两种类型:一种是基于端口的输入源(Port-based input sources),一种是自定义输入源(Custom input sources)。基于端口的数据源监听应用的Mach端口,自定义输入源监听自定义的事件源,二者的唯一区别是:基于端口的输入源的信号是由内核自动触发的,自定义输入源的信号是由其它线程手动触发的。
  • Port-Based Sources
    Cocoa 和 Core Foundation框架给我们提供了一些创建基于端口的源的对象和函数。
    在 Cocoa中,我们只需要创建一个 NSPort对象,并将其添加到运行循环中就可以了,NSPort对象会自己创建输入源。

     NSPort *port = [NSPort port];
     [port scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    但是在Core Foundation中,我们就需要手动创建端口和输入源,我们可以用CFMachPortRef, CFMessagePortRef, or CFSocketRef来创建相应的对象

  • Custom input sources
    我们也可以自定义输入源,自定义输入源时,我们需要配置定义输入源的行为,运行循环模式,输入源事件的传递机制,以及销毁输入源等。

  • Cocoa Perform Selector Sources
    除了基于端口的源,Cocoa也定义了自定义输入源--Perform Selector Sources,这使得我们可以在任意线程中执行我们的方法,Perform Selector 的方法在目标线程中是串行执行的,这样就解决了多个方法在一个线程中执行的同步问题,和基于端口的源不同,Perform Selector 在方法执行完成之后,会将自己从runloop中移除。

Note: 当我们在目标线程上执行我们的方法时,目标线程的运行循环必须开启,否则方法不会执行。主线程的运行循环默认开启,子线程的运行循环默认不开启,因此我们想要在子线程上运行我们的程序,需要手动开启子线程的运行循环。

  • Timer Sources(定时器源)
    定时器源发送同步事件到我们的线程中,对于线程来说,定时器是一种通知自己做一些事情的方式。虽然,定时器源会发出基于时间的通知,但是定时器并不是实时机制,跟输入源一样,定时器源也跟runloop的运行模式相关,如果定时器的模式,不在运行循环当前监控的模式中,定时器方法是不会执行的。另外如果定时器触发时,运行循环正在执行操作,定时器将会在下次运行循环调用时触发。
Run Loop Observers

运行循环在运行时,会发出一些通知,我们可以通过注册Observers来监听运行循环当前所处的状态,运行循环状态有以下几种:

  • 即将进入运行循环
  • 运行循环将要处理定时器
  • 运行循环将要处理输入源
  • 运行循环将要进入睡眠
  • 运行循环被唤醒,但是还未处理事件
  • 运行循环退出
The Run Loop Sequence of Events(运行循环执行事件的顺序)
  1. 通知观察者即将进入运行循环
  2. 通知观察者将要处理定时器事件
  3. 通知观察者将要处理输入源(基于端口的源除外)事件
  4. 处理输入源(基于端口的源除外)事件
  5. 如果有基于端口的输入源事件,则立即处理。然后跳到第9步。
  6. 通知观察者线程将要进入休眠
  7. 线程进入休眠,等待被唤醒,下列事件可以唤醒线程
  • 基于端口的源的事件
  • 定时器事件
  • 设置的runloop超时时间到期
  • runloop主动被唤醒
  1. 通知观察者线程刚被唤醒
  2. 处理挂起的事件
  • 如果有用户定义的定时器事件触发,处理定时器事件,并且重启运行循环,进入第2步
  • 如果触发的是输入源事件,传送事件
  • 如果runloop被主动唤醒,并且没有超时,重启runloop,进入第2步.
  1. 通知观察者运行循环将要退出

代码示例后续会整理分享

你可能感兴趣的:(深入理解runloop)