2.2 更易用的多线程——GCD
在iOS 4.0之后,GCD开始以飞快的普及率出现在众多开发者的代码中。由于它提供了更易于使用的并发模型,而不仅仅是上面讲到的线程锁,这就使得很多线程处理的陷阱得以有效避免,同时在libdispatch库中,苹果提供了大量的API,大大降低了程序员的使用难度,以及提高功能的健壮性,从而很少开发人员的青睐。
2.2.1 GCD是什么?
谈到GCD(Grand Central Dispatch)很多开发者都不陌生,这是一个与block语法块结合十分紧密的多线程技术,它拥有丰富的语言特性,多样的库供开发者使用,对于硬件上多核处理器提供了广泛地系统级别的优化,在多核的iOS和OS X硬件体系中,高并发执行能力得到有效地提升。同时它提供了一种抽象的线程,而这种抽象是基于“调度队列”(dispatch queue)。开发者不需要关心线程中具体实现了哪些细节,因为系统框架已经将block语法块加入到队列中,由GCD统一负责处理所有事务。
GCD是基于block语法块与c库函数的技术。如果想熟练使用GCD必须首先对block有充分的了解,不然很容易造成很多问题,比如本文之前提到的内存的引用环,这在如果线程中发生这种问题是十分致命的,因为开发者很难掌控子线程的生命周期,或者说,开发者并不能准确地确定线程中地代码何时能执行完毕,这就会到这大量内存泄漏。所以在学习GCD前,首先了解block这个至关重要。
2.2.2 什么是Block
Block语法块这个概念由来已久,也并非只有objective-c的语法独有的,这个语言的特性是作为延展加入到GCC编译器中的。从技术层面来讲block更接近与c语言层面的特性。在Mac OS X 10.6以及iOS4.0之后的版本之后支持block语法。在java,或是近来比较火热的swift语法中,block对象会被称之为“闭包”(closure)。而在oc中,开发者更习惯将他叫做“语法块”,或者直接称之为“块”。
Block的语法特点:
- 如同c语言函数一样,拥有参数以及参数类型,同时拥有返回值与返回值类型。
- 加入到块中的语法将会被块强引用,并持有,直到块被销毁。
- 可以捕捉它所定义的作用域范围的状态,并传递到其他作用域。
使用块的场景有很多,比如作为传统的函数回调,以及访问所需要的作用域内的局部变量,而不是需要使用一个开发者想要执行操作时集成“上下文”(context)的数据来进行回调。
2.2.3 Block基础知识
Block语法和标准c语言函数极为类似,但不同的是,Block语法块的作用域处于定义它的函数之中。如下代码。
/** * 构建一个加法运算的block语法块 * * @param firstNum 输入第一个字符 * @param secondNum 输入第二个字符 * * @return 返回两个数的和 */ int (^sumNumbers)(int firstNum,int secondNum) = ^(int firstNum,int secondNum){ return firstNum+secondNum; }; /** * 调用block语法 * 计算10+13之和 */ NSLog(@"sumNumbers = %d",sumNumbers(10,13)); 打印结果:2015-06-23 16:59:06.511 iOS性能优化[54212:628205] sumNumbers = 23
如果在Block作用域之外的一个变量需要在Block块中修改自身的值,就需要用到__block这个修饰符了,如下代码。
/** * 定义一个外部变量 */ int __block multNumbers = 0; /** * 构建一个加法运算的block语法块 * * @param firstNum 输入第一个字符 * @param secondNum 输入第二个字符 * * @return 返回两个数的和 */ int (^sumNumbers)(int firstNum,int secondNum) = ^(int firstNum,int secondNum){ multNumbers = firstNum*secondNum; return firstNum+secondNum; }; /** * 调用block语法 * 计算10+13之和 * 输出修改后的multNumbers的值 */ NSLog(@"sumNumbers = %d , multNumbers = %d",sumNumbers(10,13),multNumbers);
如果不加这个修饰符的话,xcode则会抛出这样的错误,如图2.4。
图2.4 不使用修饰符__block xcode抛出错误
此外Block语法还可以通过外部声明,在不同的地方是使用同一个命名的block,这种方法极为常用,而且十分灵活,如下代码。
enum CountNumbersType { CountNumbersTypeSum, //加法 CountNumbersTypeSub, //减法 CountNumbersTypeMult, //乘法 CountNumbersTypeDivision //除法 }; //定义枚举类型 typedef enum CountNumbersType CountNumbersType; /** * 声明计算block * * @return 返回计算结果 */ typedef int(^CountNumbers)(int,int,CountNumbersType); { /** * 定义block内方法 * * @param firstNum 第一个参数数字 * @param secondNum 第二个参数数字 * @param type 运算类型枚举值 * * @return 运算返回值 */ CountNumbers countNumbers = ^(int firstNum,int secondNum,CountNumbersType type){ switch (type) { case CountNumbersTypeSum: return firstNum + secondNum; break; case CountNumbersTypeSub: return firstNum - secondNum; break; case CountNumbersTypeMult: return firstNum * secondNum; break; case CountNumbersTypeDivision: return firstNum / secondNum; break; default: return 0; break; } }; /** * 打印输出结果 * * @param sum 相加结果 * @param sub 相减结果 * @param mult 相乘结果 * @param division 相除结果 * * @return 打印输出结果 */ NSLog(@"sum = %d,sub = %d,mult = %d,division = %d.", countNumbers(10,5,CountNumbersTypeSum), countNumbers(10,5,CountNumbersTypeSub), countNumbers(10,5,CountNumbersTypeMult), countNumbers(10,5,CountNumbersTypeDivision)); } 打印结果:2015-06-23 18:19:12.016 iOS性能优化[58960:679603] sum = 15,sub = 5,mult = 50,division = 2.
在很多情况下,block对象还可以被当作参数进行传递,下面要讲的GCD就是这样做的,还有AFNetWork 2.0使用的参数也是block回调,当然开发者也可以自己写这样的方法,如下模拟网络请求代码。
通过上面代码可以看出,使用block作为回调函数,使用起来十分简便,但使用block一定需要注意的是:
避免内存管理上面的循环引用。
注意block的异步性,这里提到的异步性和多线程的异步并不相同,而是代码执行方面,block块中的代码执行于它所定义的代码区域并非顺序执行,这点尤为需要注意,否则会造成问题。