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的语法特点:

  1. 如同c语言函数一样,拥有参数以及参数类型,同时拥有返回值与返回值类型。
  2. 加入到块中的语法将会被块强引用,并持有,直到块被销毁。
  3. 可以捕捉它所定义的作用域范围的状态,并传递到其他作用域。

使用块的场景有很多,比如作为传统的函数回调,以及访问所需要的作用域内的局部变量,而不是需要使用一个开发者想要执行操作时集成“上下文”(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块中的代码执行于它所定义的代码区域并非顺序执行,这点尤为需要注意,否则会造成问题。

posted @ 2016-08-16 10:37  徐栋  阅读(636)  评论(3编辑  收藏  举报