首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 操作系统 >

iOS多线程编程Part 一/3 - NSThread & Run Loop

2014-01-08 
iOS多线程编程Part 1/3 - NSThread & Run LoopCore Foundation层对应的是CFRunLoopRef:两组接口差不多,不

iOS多线程编程Part 1/3 - NSThread & Run Loop

Core Foundation层对应的是CFRunLoopRef:

iOS多线程编程Part 一/3 - NSThread & Run Loop

两组接口差不多,不过功能上还是有许多区别的,例如CF层可以添加自定义Input Source事件源(CFRunLoopSourceRef)和Run Loop观察者Observer(CFRunLoopObserverRef),很多类似功能的接口特性也是不一样的。

Run Loop运行

Run Loop如何运行呢?在上一节NSThread的入口函数中使用了一种NSRunLoop的使用场景,再看一例:

从上图可以看出Run Loop就是处理事件的一个循环,不同的是Timer Source事件处理后不会使Run Loop结束,而Input Source事件处理后会让Run Loop退出。因此你需要自己的一个Loop去不断运行Run Loop来处理事件,就像本文开头的示例那样。

细分下Run Loop的事件源:

1) Timer Souce就是创建Timer添加到Run Loop中,没啥好说的,Cocoa或者Core Foundation都有相应接口实现。需要注意的是scheduledTimerWith****开头生成的Timer会自动帮你以默认NSDefaultRunLoopMode模式加载到当前的Run Loop中,而其他接口生成的Timer则需要你手动使用-addTimer:forMode添加到Run Loop中。需要额外注意的是Timer的触发不会让Run Loop返回。(Timer sources deliver events to their handler routines but do not cause the run loop to exit.) 具体实验可以看下面的Sample Code。

2) Input Source中的-performSelector:***API调用簇方法,有以下这些接口:

主线程持有包含子线程的Run Loop和Source的context对象,还有一个用于保存需要运行操作的数据buffer。主线程需要子线程干活时,首先将需要的操作数据添加到数据buffer,然后通知source,唤醒子线程Run Loop(因为子线程可能正在sleep状态,CFRunLoopWakeUp唤醒Run Loop可以通知线程醒来干活),由于子线程也持有这个source和数据buffer,因此在触发唤醒时可以使用这个数据buffer的数据来执行相关操作(需要注意数据buffer访问时的同步)。

具体实现参见本文最后的Sample Code。

Run Loop的Observer

Core Foundation层的接口可以定义一个Run Loop的观察者在Run Loop进入以下某个状态时得到通知:

  • Run loop的进入
  • Run loop处理一个Timer的时刻
  • Run loop处理一个Input Source的时刻
  • Run loop进入睡眠的时刻
  • Run loop被唤醒的时刻,但在唤醒它的事件被处理之前
  • Run loop的终止

    Observer的创建以及添加到Run Loop中需要使用Core Foundation的接口:

    };

    对应Run Loop的各种事件,kCFRunLoopAllActivities比较特殊,可以观察所有事件。具体样例代码请参考Sample Code。

    总结

    Run Loop就是一个处理事件源的循环,你可以控制这个Run Loop运行多久,如果当前没有事件发生,Run Loop会让这个线程进入睡眠状态(避免再浪费CPU时间),如果有事件发生,Run Loop就处理这个事件。Run Loop处理事件和发送给Observer通知的流程如下:

    • 1) 进入Run Loop运行,此时会通知观察者进入Run Loop;
    • 2) 如果有Timer即将触发时,通知观察者;
    • 3) 如果有非Port的Input Sourc即将e触发时,通知观察者;
    • 4)触发非Port的Input Source事件源;
    • 5)如果基于Port的Input Source事件源即将触发时,立即处理该事件,跳转到步骤9;
    • 6)通知观察者当前线程将进入休眠状态;
    • 7)将线程进入休眠状态直到有以下事件发生:基于Port的Input Source被触发、Timer被触发、Run Loop运行时间到了过期时间、Run Loop被唤醒。
    • 8) 通知观察者线程将要被唤醒。
    • 9) 处理被触发的事件:
      • 如果是用户自定义的Timer,处理Timer事件后重新启动Run Loop进入步骤2;
      • 如果线程被唤醒又没有到过期时间,则进入步骤2;
      • 如果是其他Input Source事件源有事件发生,直接处理这个事件;
      • 10)到达此步骤说明Run Loop运行时间到期,或者是非Timer的Input Source事件被处理后,Run Loop将要退出,退出前通知观察者线程已退出。

        什么时候需要用到Run Loop?官方文档的建议是:

        • 需要使用Port或者自定义Input Source与其他线程进行通讯。
        • 需要在线程中使用Timer。
        • 需要在线程上使用performSelector*****方法。
        • 需要让线程执行周期性的工作。

          我个人在开发中遇到的需要使用Run Loop的情况有:

          • 使用自定义Input Source和其他线程通信
          • 子线程中使用了定时器
          • 使用任何performSelector*****到子线程中运行方法
          • 使用子线程去执行周期性任务
          • NSURLConnection在子线程中发起异步请求

            Sample Code

            RunLoop刚开始用确实坑很多,理解概念最好的方式还是动手写代码,写了个例子放在GitHub上(工程NSThreadExample),欢迎大家讨论。

            Apple官方也有一个基于Run Loop的异步网络请求示例程序SimpleURLConnections。

            参考资料

            Threading Programming Guide

            NSRunLoop Class Reference

            CFRunLoop Reference

            CFRunLoopObserver Reference

热点排行