Block 的使用

作者: ios_geek | 来源:发表于2016-10-26 15:25 被阅读169次

    **Block **是iOS在4.0之后新增的语法,在iOS SDK 4.0之后,block几乎出现在所有新版的API之中,换句话说,如果不了解block这个概念就无法使用SDK 4.0版本以后的新功能,所以我们有必要去学习下block的使用了,英文比较好的童鞋可以自行参照官方文档,本文中的示例采摘于官方文档.

    1. Block的定义
      在这一小节我们先用一些简单范例来引入block的概念。

    1.1 声明和创建Block:
     我们使用「^」运算子来声明一个block,而且在block的定义最后面要加上「;」来表示一个完整的述句,下面是一个block的范例:

    int multiplier = 7;
    int (^myBlock)(int) = ^(int num){
              return num * multiplier;
    };
    

    例子中我们声明了一个myBlock的代码块,用「^」符号来表示这是一个block。声明告诉我们myBlock是一个入参和出参都为整型(int)的block。

    值得注意的地方是block可以使用和本身定义范围相同的变数,在上面的例子中multiplier 和myBlock 都是某一个函数内定义的两个变量,都在某个函数两个大括号「{」和「 }」中间的区块,因此它们的有效范围是相同的,所以在block中就可以直接使用 multiplier 这个变数,此外当把block定义成一个变量的时,我们可以直接像使用一般函数般的方式使用它:

    int multiplier = 7 ;
    int (^myBlock)( int ) = ^( int num){
     return num * multiplier;
    };
    printf ( "%d" , myBlock( 3 ));
    //结果会打印出21
    

    1.2 直接使用Block
     在很多情况下,我们并不需要将block声明为变量,反之我们可以直接在需要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中AFNet的一个函数,就是直接使用block做为它的参数:

    - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                        success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                        failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
    {
    }
    

    1.3 __block 变量
      一般来说,在block内只能读取在同一个作用域的变量而且没有办法修改在block外定义的任何变量,如果我们想要这些变量能够在block中被修改,就必须在前面挂上__block的修饰词,以上面第一个例子中的 multiplier 来说,这个变数在 block 中是只读的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我们在 block 中修改 multiplier,在编译时就会报错,因此若要在 block 中修改 multiplier ,就必须在 multiplier 前面加上__block的修饰词,请参考下面的范例:

    __block int multiplier = 7;
         int (^myBlock)(int) = ^(int num){ 
                    if (num > 5 ) {
                    multiplier = 7 ;
                    }
                    else {
                    multiplier = 10 ; 
                    } 
                    return num * multiplier; 
      };
    printf ( "%d" , myBlock( 3 ));
    //结果会打印出30
    
    1. Block概要
       Block 提供我们一种能够将函数程式码内嵌在一般述句中的方法,在其他语言中也有类似的概念称做「closure」,也就是通常我们说的闭包的概念。

    2.1 Block 的功能
     Block 是一种具有匿名功能的内嵌函数,它的特性如下: 如一般的函数般能拥有带有型态的参数。拥有回传值。可以撷取被定义的词法作用域(lexical scope)状态。可以选择性地修改词法作用域的状态。
    注:词法作用域(lexical scope)可以想像成是某个函数两个大括号中间的区块,这个区块在程式执行时,系统会将这个区块放入堆叠记忆体中,在这个区块中的宣告的变数就像是我们常听到的区域变数,当我们说block可以撷取同一词法作用域的状态时可以想像block变数和其他区域变数是同一个层级的区域变数(位于同一层的堆叠里),而block的内容可以读取到和他同一层级的其他区域变数。我们可以拷贝一个block,也可以将它丢到其他的执行绪中使用,基本上虽然block在iOS程式开发中可以使用在C/C++开发的程式片段,也可以在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。
    2.2 Block 的使用时机
     Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
    一、可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。二、可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。

    1. 声明和创建Block
      3.1 声明一个Block变量的参考
    // 入参为空,出数也为空的block
    void(^blockReturnVoidWithVoidArgument)(void);
    // 出参为整型(int),两个入参分别是整型(int)和字符类型(char)的block
    int(^blockReturnIntWithIntAndCharArguments)(int,char);
    //出参为空,含有10个block的数组,每个block都有一个类型为int的入参
    void(^arrayOfTenBlocksReturnVoidWinIntArgument[10])(int);
    

    3.2 创建一个Block
     我们使用「^」声明一个block,并在最后使用「;」来表示结束,下面的范例示范了一个block变量,然后再定义一个block把它指定给block变量:

    /* 声明block 变量*/
    int (^oneBlock)(int);
    /* 定义一个block 并指定给上面声明的变量*/
    oneBlock = ^(int anInt)
    { 
            return anInt = - 1 ; 
    };
    
    

    3.3 全局的Block
     声明一个全局的block,请参考以下范例:

      int GlobalInt = 0;
      int(^getGlobalInt)(void) = ^(void){
             return GlobalInt;
     }
    
    1. Block和变量
      4.1 变量的形态
      我们可以在block中遇到平常在函数中会遇到的变量类型:
            全域(global)变量或是静态的区域变量(static local)。
    
             全域的函数。
             区域变量和由封闭领域(enclosing scope)传入的参数。
             除了上述之外block额外支援了另外两种变量:在函数内可以使用**__block** 变量,这些变量在block中是可被修改的。汇入常数(const imports)。
             此外,在方法的实作里,block可以使用Objective-C的实体变量(instance variable)。
       下列的规则可以套用到在block中变量的使用:
            可以存取全域变量和在同一领域(enclosing lexical scope)中的静态变量。
            可以存取传入block的参数(使用方式和传入函数的参数相同)。
            在同一领域的区域变数在block中将视为常数(const)。
            可以存取在同一领域中以__block 为修饰词的变数。
            在block中宣告的区域变数,使用方式和平常函数使用区域变数的方式相同。
            下面的例子介绍了区域变数(上述第三点)的使用方式:
    
     int x = 123;
     void (^printXandY)(int) = ^(int y)
     {
         printf("%d##%d",x,y);
     } 
     //printXAndY(456);将会输出123##456
    

    4.2 __block 型态变量
     我们可以藉由将一个由外部汇入block的变量放上修饰词__block来让这个变数由只读变成读写,不过有一个限制就是传入的变量在记忆体中必须是一个占有固定长度记忆体的变数__block修饰词无法使用于像是变动长度的阵列这类不定长度的变数,请参考下面的范例:

    //加上__block 修饰词,所以可以在block 中被修改。
    __block int x = 123;
    void (^printXandY)(int) = ^(int y){
              x = x+y;
              printf("%d %d",x,y);
    }
    printXAndY( 456 ); // 将会印出579 456
    

    下面我们使用一个范例来介绍各类型的变数和block之间的互动:

    extern NSInteger CounterGlobal;
    static NSInteger CounterStatic;
    { 
              NSInteger localCounter = 42 ; 
              __block char localCharacter;
             void(^aBlock)(void) = ^(void){
             ++ CounterGlobal; //可以存取 
             ++ CounterStatic; //可以存取
             CounterGlobal = localCounter; //localCounter在block 建立时就不可变了
             localCharacter = 'a' ; //设定外面定义的localCharacter 变量
             }
             ++localCounter; //不会影响的block 中的值
             localCharacter = 'b';
             aBlock(); //执行block 的内容
             //执行完后,localCharachter 会变成'a'
    }
    

    4.3 block 中的引用计数问题
    我们在block中引用外部的变量,在一般的情况下它将会自动增加变量的引用计数,不过若以__block作为修饰,引用计数不受影响,因此我们需要注意两点
      1. 若直接引用实例变量(instance variable),self的引用计数加1。
      2.若通过变量存取实例变数的值,只是实例变量的引用计数加1。
       以下范例说明上面两种情况,假设instanceVariable是实体变量:

    dispath_async(queue,^{
      //因为直接存取实体变量instanceVariable ,所以self 的retain count会加1
             doSomethingWithObject (instanceVariable);
     });
    id localVaribale = instanceVariable;
    dispatch_async(queue,^{
           //localVariable是存取值,所以这时只有localVariable 的retain count 加1
           //self 的return count并不会增加。
           doSomethingWithObject (localVaribale);
    });
    
    1. 使用Block
      5.1 直接使用
      我们可以像使用一般函数的方式来使用它,请参考下面两个范例:
    int(^aBlock)(int) = ^(int aInt){
              return aInt-1;
    };
    printf("10 minus 1 is %d", aBlock(10));
    //结果会显示:10 minus 1 is 9
    float(^distanceTraveled)(float, float, float) = ^(float startingSpeed, float acceleration, float time){
              float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
              return distance;
    };
    float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
    //howFar的值为4.9
    

    在一般常见的情况中,若是将block当做是参数传入函数,我们通常会使用「内嵌」的方式来使用block。
    5.2 将Block当作函数的参数
    我们可以像一般函数使用参数的方式,将block以函数参数的形式传入函数中,在这种情况下,大多数我们使用block的方式将不会倾向声明block而是直接以内嵌的方式来将block传入,这也是目前SDK中主流的做法:

    char *myCharacters[3]={"TomJohn","George","Charles Condomine"};
    qsort_b(myCharacters, 3, sizeof(char *),^(const void *l,const void *r){
               char *left = *(char **)l;
               char *right = *( char **)r;
               return strncmp (left, right, 1 );
    }//这里是block 的终点。 
    );
    //最后的结果为:{"Charles Condomine", "George", "TomJohn"}
    

    相关文章

      网友评论

        本文标题:Block 的使用

        本文链接:https://www.haomeiwen.com/subject/kcnduttx.html