1 Overview


本文内容是个人在iOS上的异步编程的一些初步的心得体会,不保证完全正确,如有问题,欢迎指教。


2 运行模型


异步编程从来是与硬件和系统紧密相关的, 不同的硬件架构和不同的系统架构有不同的异步模型, 自然也会带来不同的异步编程方式。 要深究iOS上的异步编程, 首先就要了解iOS的异步模型。


2.1 事件驱动


“事件驱动”是iOS的异步模型中一个很重要的概念,也是iOS程序运行的概念。
所谓“事件驱动”本质上与Windows的“消息驱动”是相同的。 一个iOS程序至少有一个事件队列, 主程序干的唯一一件事情就是不断从这个事件队列中取出各种事件, 并调用对应的事件处理函数。 这 与windows程序的处理方式——消息驱动本质上是一模一样的。


2.2 Runloop


Runloop是iOS中的一个重要的概念, 在异步编程中常常要和它打交道。 而实质上, Runloop等价于 win32中的WinProc函数。 或者说, 它们干了一样的事情——从消息/事件队列中取出消息, 根据消息/ 事件的类型调用对应的回调函数。 所不同的是, win32下, 消息处理对用户是开放的——用户可以自 定义消息类型, 并向消息队列中插入这些消息并进行相应的处理。 而在iOS下, 事件完全被隐藏在在 runloop之中。 用户只知道有runloop的存在, 而runloop中的具体细节, 用户是无法接触的。


2.3 Delegate


既然用户无法接触事件的具体细节, 那么用户又怎么和runloop互动,利用iOS的事件驱动机制呢? Apple对此的回答是:delegate! Cocoa Touch中提供的各种UI控件, 几乎都有一个delegate成员, 同 时有一个对应的protocol:UIxxxDelegate。 这样,Apple就把事件隐藏了起来。 当runloop从事件队列 中取到事件时, 它会寻找到对应的控件实例, 然后调用对应的delegate成员的对应的处理函数, 也 就是客户提供的各种delegate中的函数。 当然,注册事件的回调函数并不仅仅有delegate这个死板的 方式。 对UIControl, 小气的Apple还是犹抱琵琶半遮面地提供了一些和Win32类似的Tag的——UICon- trolEvents。 这就是另一种注册事件回调函数的方式:[UIControl addTarget:action:forEvent:]。 当 然,回调的过程还是差不多的——runloop取事件, 取对象,调用相应的回调处理函数。


2.4    Single Thread!


iOS并不喜欢程序员使用多线程, Apple自己也很讨厌多线程, 于是iOS的很多机制都是单线程的, 这就包括runloop, 还有autorelease pool等等。 可是这些都是非常基础的机制。 所以,编程中应该 尽可能地使用单线程。


2.5 小结

iOS的运行模型可以归结为: 以单线程的事件驱动模型。 系统将用户的各种操作和系统的各种IO归 结各种事件, 放入统一的事件队列中, 由runloop统一处理。 用户可以通过各种delegate或者actions 或者notification等指定事件的处理方式, 但不能直接接触事件。 更不能自定义事件。 这种事件驱动 机制以单线程为基础。 也以单线程为边界。 系统只会为主线程自动添加事件队列, 而其他线程则需要 手动添加。 其他线程中的消息是不会自动被添加到主线程中的消息队列的, 除非手动指定。 所以才会 有那么多perform:inMainThreadXXX之类的work-around。 实际上就是往Main Thread里的事件队列里面 加事件而已。 这种单线程的消息驱动实际上是一种很古老的运行模型。 早期没有线程概念的时候,几 乎所有的GUI程序都是这么干的。 最多一个叫event,一个叫message而已。

3 异步编程

3.1 基本的概念


从上面的运行模型可以得到一些关于iOS上的异步编程的基本的概念。 首先,与PC上的异步编程的概 念不同, iOS上的异步更多的指的是操作时序上的不确定,而非并发操作。 也因此,在大部分情况下是 不需要担心并发操作带来的各种资源冲突和死锁问题的。 其次, iOS程序中, 几乎每一行代码都是由 各种事件引发的, 又是单线程, 因此会引发runloop的阻塞。 因此每个函数的执行时间应该尽可能的 短, 以便系统继续runloop。 第三, 在单次的事件处理中, 是不需要担心资源冲突的问题的, 因为 这时没有第二个线程会来访问这些资源——除非你非要用多线程。 但是, 在多次的事件处理中, 却必 须考虑资源冲突的问题。


3.2 事件种类


iOS的事件种类大约有以下几种:
1. 用户操作,比如各种手势、触摸、按键等等
2. Timer,专门的timer事件,用于各种定时器。从这里也可以看出,iOS的timer是“伪”timer,只 要runloop被阻塞了,那么timer永远不会被引发。
3. I/O事件,比如各种文件的读写和网络的读写等,异步IO是一个很节约CPU资源的东西,iOS采用异 步IO也在情理之中。


3.3 常见的资源冲突


事实上iOS上绝大部分的资源冲突来自于事件顺序的不确定。 比如异步的网络读取和用户操作对同一 个资源的操作导致的操作顺序的不确定。
而由于iOS是事件驱动的,除非在函数中使用了runloop,否则不需要担心资源在函数之外被改写。