第6章
block 与 GCD
当前在开发应用程序时,每位程序员都应该留意多线程问题。你可能会说自己要开发的应用程序用不到多线程,即便如此,它也很可能是多线程的,因为系统框架通常会在 UI 线程之外再使用一些线程来执行任务。开发应用程序时,最糟糕的事情莫过于程序因 UI 线程阻塞而挂起了。在 Mac OS X 系统中,这将使鼠标指针一直呈现令人焦急的旋转彩球状;而在 iOS 系统中,阻塞过久的程序可能会终止执行。
所幸苹果公司以全新方式设计了多线程。当前多线程编程的核心就是 “块”(block)与 GCD(Grand Central Dispatch, GCD)。这虽然是两种不同的技术,但它们是一并引入的。“块”是一种可在 C 、C++及 Objective-C 代码中使用的 “词法闭包”(lexical closure),它极为有用,这主要是因为借由此机制,开发者可将代码像对象一样传递,令其在不同环境(context)下运行。还有个关键的地方是,在定义“块”的范围内,它可以访问到其中的全部变量。
GCD 是一种与块有关的技术,它提供了对线程的抽象,而这种抽象则基于“派发队列”(dispatch queue)(亦称调度队列)。开发者可将块排入队列中,由 GCD 负责处理所有的调度事宜。GCD 会根据系统资源情况,适时地创建、复用、摧毁后台线程(background thread),以便处理每个队列。此外,使用 GCD 还可以方便地完成常见编程任务,比如编写 “只执行一次的线程安全代码”(thread-safe single-code execution),或者根据可用的系统资源来并发执行多个操作。
block 与 GCD 都是当前 Objective-C 编程的基石。因此,必须理解其工作原理及功能。
本条要点:(作者总结)
块可以实现闭包。这项语言特性是作为“扩展”(extension)而加入 GCC 编译器中的,在近期版本的 Clang 中都可以使用 (Clang 是开发 Mac OS X 及iOS 程序所用的编译器)。10.4 版及其后的 Mac OS X 系统,与 4.0 版及其后的iOS 系统中,都含有正常执行块所需的运行期组件。从技术上讲,这是个位于C 语言层面的特性,因此,只要有支持此特性的编译器,以及能执行块的运行期组件,就可以在 C、C++、Objective-C 、Objective-C++ 代码中使用它。
块的基础知识
块与函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围内的东西。块用 “^” 符号(caret,可称为脱字符或插入符)来表示,后面跟着一对花括号,括号里面是块的实现代码,例如,下面就是个简单的块:
1 ^{ 2 3 // Block implementation here 4 }
块其实就是个值,而且自有其相关类型。与 int、float 或 Objective-C 对象一样,也可以把块赋给变量,然后像使用其他变量那样使用它。块类型的语法与函数指针近似。下面列出的这个块很简单,没有参数,也不返回值:
1 void (^someBlock)() = ^{ 2 3 // Block implementation here 4 };
这段代码定义了一个名为 someBlock 的变量。由于变量名写在正中间,所以看上去也许有点怪,不过一旦理解了语法,很容易就能读懂。块类型的语法结构如下:
1 return_type (^block_name) (parameters)
下面这种写法所定义的块,返回 int 值,并且接受两个 int 做参数:
1 int (^addBlock)(int a, int b) = ^(int a, int b){ 2 3 return a + b; 4 }
定义好之后,就可以像函数那样使用了。比方说,addBlock 块可以这样用:
int add = addBlock(2, 5); // < add = 7
块的强大之处是:在声明它的范围里。所有变量都可以为其所捕获。这也就是说,那个范围里的全部变量,在块里依然可用。比如,下面这段代码所定义的块,就使用了块以外的变量:
1 int additional = 5; 2 3 int (^addBlock)(int a, int b) = ^(int a, int b){ 4 5 return a + b + addItional; 6 }; 7 8 int add = addBlock(2, 5); // < add = 12
默认情况下,为块所捕获的变量,是不可以在块里修改的。在本例中,假如块内的代码改动了 additional 变量的值,那么编译器就会报错。不过,声明变量的时候可以加上 __block 修饰符,这样就可以在块内修改了。例如,可以用下面这个块来枚举数组中的元素,以判断其中有多少个小于 2 的数:
1 NSArray *array = @[@0, @1, @2, @3, @4, @5]; 2 3 __block NSInteger count = 0; 4 5 [array enumerateObjectsUsingBlock: 6 7 ^(NSNumber *number, NSUInteger idx, BooL *stop) { 8 9 if([number compare:@2] == NSOrderedAscending) { 10 count++; 11 } 12 }] ; 13 // count = 2
这段范例代码也演示了 “内联块”(inline Block)的用法。