iOS runloop 学习笔记(一) - 官方文档

先贴下 apple doc, 本文基本是对照 doc 的翻译:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

RunLoops 是 thread 线程的底层基础.它的本质是和它的意思一样运行着的循环,更加确切的说是线程中的循环.它用来接受循环事件和安排线程的工作,并且在线程中没有工作时,让线程进入睡眠状态.

RunLoops 并非完全自动管理的. 你可以在自己开辟的新thread 中使用runloop 来帮助你处理incoming 事件. iOS 中 Cocoa 和 CoreFoundation 框架中各有完整的一套关于 runloop 对象的操作api.在主线程中RunLoop 是系统创建的,在子线程中你必须手动去生成一个 runloop.

相关内容: NSRunLoop CFRunLoop

什么是 RunLoops

在新建 xcode 生产的工程中有如下代码块:

int main(int argc, char * argv[]) {
     @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
    }
}

当程序启动时,以上代码会被调用,主线程也随之开始运行,RunLoop 也会随着启动.
UIApplicationMain()方法里面完成了程序初始化,并设置程序的Delegate任务,而且随之开启主线程的 RunLoop,就开始接受事件处理.

RunLoop 是一个循环,在里面它接受线程的输入,通过事件处理函数来处理事件.你的代码中应该提供 while or for 循环来驱动 runloop.在你的循环中,用 runloop 对象驱动事件处理相关的内容,接受事件,并做响应的处理.

RunLoop 接受的事件源有两种大类: 异步的input sources, 同步的 Timer sources. 这两种事件的处理方法,系统所规定.

官方文档中有如下贴图,解释了 runloop 的基本结构



从图中可以看出,RunLoop 是线程中的一个循环,并且对接受到的事件进行处理.我们的代码可以通过提供 while 或者 for 循环来驱动 RunLoop.在循环中,Run Loop 对象来负责事件处理的代码(接受事件,并调用相应的事件处理方法).

RunLoop 从以下两个不同的事件源中接受消息:

  • InputSources : 用来投递异步消息,通常消息来自另外的线程或者程序.在接受到消息并调用指定的方法时,线程对应的 NSRunLoop 对象会通过执行 runUntilDate:方法来退出.
  • Timer Source: 用来投递 timer 事件(Schedule 或者 Repeat)中的同步消息.在消息处理时,并不会退出 RunLoop.
  • RunLoop 除了处理以上两种 Input Soruce,它也会在运行过程中生成不同的 notifications,标识 runloop 所处的状态,因此可以给 RunLoop 注册观察者 Observer,以便监控 RunLoop 的运行过程,并在 RunLoop 进入某些状态时候进行相应的操作, Apple 只提供了 Core Foundation 的 API来给 RunLoop 注册观察者Observer.

后面两部分内容主要是: RunLoop 的组成, RunLoop的 modes, 以及 RunLoop 运行过程中产生的 notifications

RunLoopModes-- RunLoop 运行的模式

RunLoopMode 可以理解成为一个集合, 包括所有要监视的事件源(前面提到的两种源)和要通知的 RunLoop 中注册的观察者.每次运行 RunLoop 时,都需要显示或者隐式的指定其运行在哪一种 Mode(RunLoop 每次只能运行在一个 mode中).在设置 RunLoopMode 以后,你的 RunLoop 就会自动过滤和其他 Mode 相关的事件源,而只监视和当前设置 Mode 相关的源(以及通知相关的观察者).大多数时候 RunLoop 都运行在系统定义的默认的模式上.

在代码中,你可以通过mode 名称区分不同的 mode. Cocoa & CoreFoundation 框架通过不同名称(NSString,CFString)定义了缺省 mode 和一系列其他的 mode.你也可以使用不同的名称,定义自己的 mode,然后在这个 mode 中添加一些 source 以及 observer.

使用这些 modes 可以从不想要的事件源中过滤事件.大多数情况下,我们都将 runloop 设置成default mode.

RunLoopMode 是基于事件的source源头,而不是事件的type类型去区分的.比如你不能通过 RunLoopMode 去只选择鼠标点击事件或者键盘输入事件.你可以使用 RunLoopMode 去监听端口,暂停计时器或者或者改变添加或删除一些 mode 中关注的 sources or observers.

Cocoa 和 CoreFoundation 为我们定义了默认和常用的 Mode.RunLoopMode 的名称可以使用字符串来标识,我们也可以使用字符串指定一个 Mode 名称来自定义 Model.

下面列出iOS 中已经定义的 RunLoopMode:

  • NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多数工作中默认的运行方式。
  • NSConnectionReplyMode: 使用这个Mode去监听NSConnection对象的状态,我们很少需要自己使用这个Mode。
  • NSModalPanelRunLoopMode: 使用这个Mode在Model Panel情况下去区分事件(OS X开发中会遇到)。
  • UITrackingRunLoopMode: 使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)。
  • GSEventReceiveRunLoopMode: 用来接受系统事件,内部的Run Loop Mode。
  • NSRunLoopCommonModes, kCFRunLoopCommomModes: 这是一个伪模式,其为一组run loop mode的集合。如果将Input source加入此模式,意味着关联Input source到Common Modes中包含的所有模式下。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode.同时,我们可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函数,将自定义的 mode 加入其中。

注意 RunLoop 运行时只能以一种固定的 Mode运行,只会监控这个 Mode 下添加的 Timer source 和 Input source.如果这个 Mode下没有添加时间源,RunLoop 就会立即返回.
RunLoop 不能运行在 NSRunLoopCommonModes,因为 NSRunLoopModes 是个 Mode 的集合,而不是一个具体的 Mode.我们可以在添加事件源的时候使用 NSRunLoopCommomModes,只要 RunLoop 运行在 NSRunLoopModes 中任何一个 Mode,这个事件源就会被触发.

RunLoop 的事件源

Input soruces 将 event 异步的发送给 threads.而event 的 source 是基于input source 的类型.Input source 有两种不同的种类: Port-Based SourcesCustom Input sources. Port-Based Sources监听 Mach Port ,Custom Input Source类型 监控着 custom source 发出的 event.这两种不同类型的 Input source 区别在于: Port-Based Sources由内核自动发送,custom sources 必须手动的从其他 thread 发出

当你创建一个 input soruce, 你需要将它放入到一个或者多个 runloop modes 中.如果一个 input source 不在当前的被监控的 mode 中, 那么这个 input source 产生的事件 Runloop 是不会收到的,除非 这个 input source 被放到了正确的 mode 中.

下文描述了几种不同的 input sources.

Port-Based Sources

Cocoa 和 CoreFoundation 框架 对 port-based input soruces 相关的对象和函数提供了内置的支持.比如Cocoa 中,你永远不必直接创建一个 input source,你可以直接通过方法创建一个 NSPort,然后直接将这个 port 对象加入到 runloop 中. NSPort 对象会负责自己创建和配置 input source.

在CoreFoundation 中,你必须手动创建 port 和与它对应的 runloop source.使用 CFMachPortRef,CFMessagePortRef,CFSocketRef 去创建合适的对象.

具体的创建 port-based soruce 的实例见:

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-131281

Custom Input Source

我们可以使用 CoreFoundation 中的 CFRunLoopSourceRef 类型相关的函数来创建 custom input sources. 你可以使用几个 callback 函数来配置 custom input source. CoreFoundation 会在几个不同的事件触发的节点来调用注册的 callback 函数,具体的节点: configuration, handle incoming event以及销毁 source.

同时,你必须定义指定当 event 到来时,custom source的行为,以及事件的传递的形式.

具体的调用实例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3

Cocoa Perform Selector Sources

Cocoa 框架为我们定义了一些 Custom Input Sources, 允许我们在任何thread中执行Selector方法.

当在一个目标 thread 中 perform selector,需要该 thread 的 runloop 必须是运行状态的.这意味着,如果这个 thread 是你创建的,那么 selector 的内容直到 runloop 启动以后才会执行,而之前通过 perform selector 加入的就会被加入到一个queue中等待执行.和Port-Based Sources一样,这些 selector 的请求会在目标线程中加入到 queue 中序列化,以减缓线程中多个方法执行带来的同步问题.
Port-Based Sources不一样的是,一个 selector 方法执行完成以后会自动从当前RunLoop 中移除.

下面是 NSObject 中定义的 perform selector 的方法

//在主线程的 RunLoop 下执行指定的 @selector 方法
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

//在当前线程的 RunLoop 下延迟加载指定的 @selector 方法
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

//在当前线程的 RunLoop 下延迟加载执行的 @selector 方法
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

//取消当前线程的调用
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

Timer sources

Timer sources在预设的时间点synchronously同步的给 thread发送 evet.Timer 是thread 通知自己做某事的一种方式. 例如, search filed 可以使用一个 timer 来每隔一段时间自动去查询用户输入的信息.

尽管, Timer sources 会生成 time-based notifications, 但是 timer 并非完全真正的基于时间点的.同 input source 一样, Timer 也是和特定的RunLoopMode 有关的.如果Timer并不在当前 RunLoop 的 mode 的监控范围内,那么 Timer 就不会被触发,直到 RunLoop 切换到 Timer 所在的 mode 中.相似的是,如果 Timer 在当前 mode 触发了,但是 RunLoopMode 又被改变了,那么后面 Timer 就仍然不会被触发.

我们可以设置 Timer 的触发方式是once 一次性 或者 repeat 重复. 一个 repeating timer 重复触发依赖的时间是基于上一次的 fire time 的,并非实际的时间(有可能中间 runloopmode 有改变).例如, 一个 timer 设定的触发时间是每隔5s 触发一次,那么每一次激活都是前一次再加5s,即使前一次 fire 时间有 delay.即使有一次 fire time 延迟了很久,可能是13s,错过了两次 fire time,那么后面仍然只 fire 一次,在timer 这次 fire 过后, timer 又重新规划下次 fire 时间在5s 后.

Cocoa 和 CoreFoundation 中 NSTimer,CFRunLoopTimer 提供了相关方法来设置 Timer sources.需要注意的是除了scheduledTimerWithTimeInterval开头的方法创建的 Timer 都需要手动添加到当前RunLoop中.(scheduledTimerWithTimeInterval 创建的 timer 会自动以 default mode 加载到当前 RunLoop 中)

Timer 在选择使用一次后,在执行完成时,会从 RunLoop 中移除.选择循环时候,会一直保存在当前 RunLoop 中,直到 invalidated 方法执行.

具体使用 timer 的实例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW6

RunLoop Observers, RunLoop 运行过程中的状态观察者

上文中提到的激活 RunLoop 的 input source 事件源, 他们会产生 同步(synchronous "timer")或者异步(asynchronous custom source等),与之对应, runloop 的 observers 将会在 runloop 运行到某个状态时候自动触发. 你可以在 runloop 即将处理某个事件时使用observers 进行某项操作,或者使用 observers 对 runloop 进入睡眠时候去控制 thread. 简而言之, RunLoop Observer 则在 RunLoop 本身进入某个状态时候得到通知

你可以创建一个 Observer 对 RunLoop 的如下状态进行观察:

  • RunLoop 进入时候
  • RunLoop 将要处理一个 Timer 时候
  • RunLoop 将要处理一个 Input Source时候
  • RunLoop 将要进入睡眠的时候
  • RunLoop 将要被唤醒的时候,在唤醒它的事件被处理之前
  • RunLoop 停止的时候

你可以用 CoreFoundation 的 API 给 RunLoop 添加 observers.使用CFRunLoopObserverRef 创建一个 runloop observer 的实例. 在创建实例时候, 给 observer 配置 callbacks 来跟踪 runloop的状态.

与 timer 类似, runloop observers 可以被声明成 once 一次 或者 repeat 重复监控. 一次性的 observer 会在它被触发,完成相关操作就从 runloop 中移除监控, repeat 类型还会继续监听. 当你创建一个 observers 你就应该指定是once 还是 repeat.

代码实例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW18

RunLoop 事件的序列

每次, RunLoop 会操作输入的 events(各种 input source 产生的) 并产生 notifications(给 observers).
RunLoop 的处理 events 并且发出 notifications 的具体的顺序如下:
1> 发 notification,通知 Observer,当 RunLoop 开始进入循环的时候
2> 发 notification,通知 Observer, timers 即将被触发(处理 timers 的event)
3> 发 notification,通知 Observer,有其他的非 Port-Based Input Source 即将被触发(non-port-based input source 的 event)
4> 启动非 Port-Based Input Soruce 的事件源的处理函数
5> 如果基于Port的Input Source事件源即将触发时,立即处理该事件,并跳转到9
6> 发 notification,通知 Observer,当前 thread 即将进入睡眠状态
7> 使线程进入睡眠状态直到有以下事件发生:
1.Port-Based Input Source event
2.Timer fires
3.RunLoop 设置的时间超时
4.RunLoop 被代码显示唤醒
8> 发 notification,通知 Observer, thread将要被唤醒
9> 处理被触发的事件
1.如果是用户自定义的Timer触发的,处理Timer事件的函数后,重启Run Loop.直接进入步骤2
2.如果是 input source 事件源有事触发 event,直接传递这个消息
3.如果runloop 是显示被唤醒并且没有超时,重启 RunLoop. 直接进入步骤2
10> 发 notification,通知 Observer,Run Loop已经退出

由于 timer 和 input source导致 runloop 给 observer 发送 notification 是在这些事件之前的, 因此可能 notification 发出以后到事件真正的发生中间会有一小段间隔时间, 可以通过 awake-from-sleep 的 notification 来修复确切的时间.

因为 timers 和其他与时间片相关的事件会在runloop 运行时候传递, 有时候会使得循环传递这些事件失灵.典型例子就是, 你在进入 runloop 时候,监听了鼠标的移动路线,然后不断在 app 中请求一些事件.因为你的代码中已经抓住这些事件,并直接处理了,而不是让 app 正常的去 dispatch事件,活动着的 timers 讲将不能被fire,直到你的鼠标追踪事件停止,并把控制权交给 app.

RunLoop 可以使用 runLoop 对象显示的唤醒. 其他的事件也能够唤醒 runloop.例如,给 runloop 添加一个 non-port-based input source,就可以唤醒它,而不必等到其他的事件发生.

何时使用 RunLoop

我们应该只在创建辅助线程的时候,才显示的运行一个 RunLoop.iOS app 会在应用启动的时候帮我 run 一个 runloop,而我们自己新建的辅助线程不会.
对于辅助线程,我们仍然需要判断是否需要启动一个 RunLoop.比如我们使用一个线程去处理一个预先定义的长时间的任务,我们应该避免启动 RunLoop.下面是官方document 提供的使用 RunLoop 的几个场景:

  • 需要使用 Port-Based Input Source或者 Custom InputSource 和其他thread通讯时
  • 需要在线程中使用 Timer
  • 需要在线程中使用上文中提到的selector相关的方法
  • 需要让线程周期性的执行某些工作

如果你选择使用 RunLoop, runloop 的设置和启动是比较直观的. 同时,你需要实现什么情况下从辅助线程中退出 runloop, 最好不要直接关闭线程,而是先退出 runloop.

如何创建和设置 runloop.代码:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW5

使用 RunLoop 对象

RunLoop 对系为增加 input source,timers,添加 observers 提供了主要的接口.每一个 thread 都有且仅有一个 runloop.Cocoa 中使用 NSRunLoop, CoreFoundation 中使用 CFRunLoopRef.

从线程中获取 RunLoop 对象

为了从当前 thread 中获取runloop 对象,具体步骤如下:

  • 在 Cocoa中, 使用 [NSRunLoop currentRunLoop] ,就会返回当前线程的 runLoop 对象.
  • CoreFoundation 中使用 CFRunLoopRef.

CFRunLoopRef和 NSRunLoop 可以转化, NSRunLoop 使用getCFRunLoop方法就可以得到 CFRunLoopRef 对象

配置 RunLoop 对象

在辅助线程启动 runloop 之前,你必须至少在其中添加一个 input source 或者 timer.如果一个 runloop 中没有一个事件源sources, runloop 会在你启动它以后立即退出.

在添加了 source 以后,你可以给 runloop 添加 observers 来监测 runloop 的不同的执行的状态.为了加入 observer, 你应该创建一个 CFRunLoopObserverRef,使用 CFRunLoopAddObserver 函数添加 observer 到你的 runloop.

下面代码块显示了, 如何给 RunLoop 添加一个 observer .

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

启动 RunLoop

在辅助线程中,启动一个 runloop 是必须的.runloop 中必须有一个 input source 或者 timer事件源.如果 runloop 启动时候,内部没有监听任何一个 input source 或者 timer, runloop 会立即 exit.

有以下几种启动 RunLoop 的方法:

  • 没有设置特定的条件启动
  • 设置一个时间限制
  • 在一个特定的 mode

最简单的是一个无条件的启动 runloop,但是这也是最差的选择.如果没有设置任何条件,就会将 runloop 所在的 thread 进行永久的事件循环.你可以增加或者减少 input sources, timers,但是只有一种方法能够 kill 掉它.同时这种方式没办法让 runloop 在自定义的 mode 中运行.

替代无条件进入run loop更好的办法是用预设超时时间来运行runloop,这样runloop运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息会被传递给相应的处理程序来处理,然后runloop退出。你可以重新启动runloop来等待下一事件。如果是规定时间到期了,你只需简单的重启runloop或使用此段时间来做任何的其他工作。

除了超时机制,你也可以使用特定的模式来运行你的runloop。模式和超时不是互斥的,他们可以在启动runloop的时候同时使用。模式限制了可以传递事件给run loop的输入源的类型,这在”Run Loop模式”部分介绍。

描述了线程的主要例程的架构。本示例的关键是说明了runloop的基本结构。本质上讲你添加自己的输入源或定时器到runloop里面,然后重复的调用一个程序来启动runloop。每次runloop返回的时候,你需要检查是否有使线程退出的条件成立。示例中使用了Core Foundation的run loop例程,以便可以检查返回结果从而确定run loop为何退出。若是在Cocoa程序,你也可以使用NSRunLoop 的方法运行run loop,无需检查返回值。

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

可以递归的运行run loop。换句话说你可以使用CFRunLoopRun,CFRunLoopRunInMode或者任一NSRunLoop的方法在输入源或定时器的处理程序里面启动run loop。这样做的话,你可以使用任何模式启动嵌套的run loop,包括被外层run loop使用的模式。

退出 RunLoop

有两种方法可以让 runloop 退出:

  • 给 runloop 设置超时事件
  • 通知 runloop 停止
    如果可以配置的话,推荐使用第一种方法。指定一个超时时间可以使run loop退出前完成所有正常操作,包括发送消息给run loop观察者。

使用CFRunLoopStop来显式的停止runloop和使用超时时间产生的结果相似。Runloop把所有剩余的通知发送出去再退出。与设置超时的不同的是你可以在无条件启动的run loop里面使用该技术。

尽管移除runloop的输入源和定时器也可能导致run loop退出,但这并不是可靠的退出run loop的方法。一些系统例程会添加输入源到runloop里面来处理所需事件。因为你的代码未必会考虑到这些输入源,这样可能导致你无法没从系统例程中移除它们,从而导致退出runloop。

线程安全和 RunLoop 对象

线程是否安全取决于你使用那些API来操纵你的runloop。CoreFoundation 中的函数通常是线程安全的,可以被任意线程调用。但是如果你修改了runloop的配置然后需要执行某些操作,任何时候你最好还是在run loop所属的线程执行这些操作。

至于Cocoa的NSRunLoop类则不像CoreFoundation具有与生俱来的线程安全性。如果你想使用NSRunLoop类来修改你的runloop,你应用在runloop所属的线程里面完成这些操作。给属于不同线程的runloop添加输入源和定时器有可能导致你的代码崩溃或产生不可预知的行为。(不要在当前线程操作其他线程的 runloop)

配置 RunLoop sources

以下部分列举了在Cocoa和Core Foundation里面如何设置不同类型的输入源的例子

自定义的 input sources

创建自定义的输入源包括定义以下内容:

  • 输入源需要处理的信息
  • 使感兴趣的客户端(可理解为其他线程)知道如何和输入源交互的调度程序
  • 处理其他任何客户端(可理解为其他线程)发送请求的程序
  • 使输入源失效的取消程序

由于你自己创建输入源来处理自定义消息,实际配置选是灵活配置的。调度程序,处理程序和取消程序都是你创建自定义输入源时最关键的例程。然而输入源其他的大部分行为都发生在这些例程的外部。比如,由你决定数据传输到输入源的机制,还有输入源和其他线程的通信机制也是由你决定。

ps: custom 源很少用...具体见
http://www.dreamingwish.com/article/ios-multithread-program-runloop-the.html

配置Timer source

为了创建一个定时源,你所需要做只是创建一个定时器对象并把它调度到你的runloop。Cocoa程序中使用NSTimer类来创建一个新的定时器对象,而Core Foundation中使用CFRunLoopTimerRef不透明类型。本质上,NSTimer类是CoreFoundation的简单扩展,它提供了便利的特征,例如能使用相同的方法创建和调配定时器。
Cocoa中可以使用以下NSTimer类方法来创建并调配一个定时器:

scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
 
scheduledTimerWithTimeInterval:invocation:repeats:

上述方法创建了定时器并以默认模式把它们添加到当前线程的run loop。你可以手工的创建NSTimer对象,并通过NSRunLoop的addTimer:forMode:把它添加到run loop。两种方法都做了相同的事,区别在于你对定时器配置的控制权。例如,如果你手工创建定时器并把它添加到run loop,你可以选择要添加的模式而不使用默认模式。下面的嗲吗显示了如何使用这这两种方法创建定时器。第一个定时器在初始化后1秒开始运行,此后每隔0.1秒运行。第二个定时器则在初始化后0.2秒开始运行,此后每隔0.2秒运行。

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

下面的代码显示了使用Core Foundation函数来配置定时器的代码。尽管这个例子中并没有把任何用户定义的信息作为上下文结构,但是你可以使用这个上下文结构传递任何你想传递的信息给定时器。关于该上下文结构的内容的详细信息,参阅CFRunLoopTimer Reference。

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

配置基于 port-based source

Cocoa和Core Foundation都提供了基于端口的对象用于线程或进程间的通信。以下部分显示如何使用几种不同类型的端口对象建立端口通信。

配置NSMachPort对象

为了和NSMachPort对象建立稳定的本地连接,你需要创建端口对象并将之加入相应的线程的run loop。当运行辅助线程的时候,你传递端口对象到线程的主体入口点。辅助线程可以使用相同的端口对象将消息返回给原线程。
ps: 实际进程间通信用的比较少,AFNetworking 2.x里面有使用.防止 runloop 停止,在启动 runloop 之前就添加了一个 NSPort source.

参考文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/
http://www.dreamingwish.com/frontui/article/default/ios-multithread-program-runloop-the.html
https://github.com/wuyunfeng/LightWeightRunLoop
https://github.com/yechunjun/RunLoopDemo
http://blog.ibireme.com/2015/05/18/runloop/

推荐阅读更多精彩内容