iOS:Block详解

一个详细的blcok文档,能够系统全面的了解和学习Block

Block-编程要点

一 、

(一)定义和使用Block
首先使用^运算子来定义一个block变数,而且在block的定义后面加上; 来表示一个完整的述句 int multiplier = 7 ;   
int (^myBlock)( int ) = ^( int num)
     {
         return num * multiplier;
     };
我们定义一个「myBlock」变数,用「^」符号来表示这是一个block。
这是block的完整定义,这个定义将会指定给「myBlock」变量。
表示「myBlock」是一个回传值为整数(int)的block。
它有一个参数,型态也是整数。
这个参数的名字叫做「num」。
这是block的内容。
打给我们把block定义成一个变数时,我们可以直接像使用一般函数的方式使用它,如下
int multiplier = 7 ;
     int (^myBlock)( int ) = ^( int num)
     {
         return num * multiplier;
     };
     printf ( "%d" , myBlock(3 ));
     //结果会打印出21
(二) 直接使用Block
在很多情况下,我们并不需要将block定义成变量,我们可以直接在需要使用block的地方用内嵌方式将block的内容写出来

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

三 、定义和创建Block
(一)定义Block的参数
Block变量存储的是一个block的参数,我们使用类似定义指针的方式来宣告,不同的是这时block变量指到的地方是一个函数,而不是指针使用的是* block则是使用^来宣告,下面是一些合法的block宣告:
void(^blockReturningWithVoidArgument)(void);// 回传void,参数也是void的block
    int(^blockReturningIntWithIntAndCharArguments)(int,char);// 回传整型,两个参数分别是整型和字符型
    void(^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);// 回传void ,含有10个block的数组,每个block都有一个整型的参数
我们使用^来开始一个block 并在最后用;表示结束下面示范一个block变量,然后定义一个block把它指定给block变量
i
nt(^oneFrom)(int);//定义block变量
    oneFrom = ^(int anInt)// 定义block的内容并指给上面定义的变量
    {
        return anInt = -1;
    };
四 、Block和变量
接下来的将介绍block和变量之间的互动
(一) 变量的型态
我们可以在block中遇到平常在函数中会遇到的变量类型
全局变量(global)或是静态的局部变量(static local)
全局的函数
局部变量和由封闭领域(enclosing scope)传入的参数
除了上述之外block额外支援另外两种变量:
在函数内可以使用__block变量,这些变量在block中可被修改
汇入常数(const imports)
此外,在方法的实际操作里。block可以使用Object—C的实体变数(instance variable)。
下列规则可以套用在block中变量的使用
可以存取全局变量和在同一领域(enclosing lexical scope)中的静态变量
可以存取传入block的参数(使用方式和传入函数的参数相同)
在同一领域的局部变量在block中视为常量(const)
可以存取在同一领域中以__block为修饰词的变量
在block中定义的局部变量使用方式和平常函数使用局部变量的方式相同
下面的例子介绍局部变量的使用方式
int x = 123;
    void(^printAAndY)(int) = ^(int y)
    {
        printf(@"%d%d",x,y);
    };
    // 将会打印出123 456
    printXAndY(456);
就如上面所提到,变量x,在传入block后视为常量,因此我们在block中试着去修改x的时候就会产生错误
下面的无法通过编译
(三)物件和Block变量
在拥有参考计数(reference-counted)的环境中,若我们在block中参考到Objective-C的物件,在一般情况下它会自动增加物件的参考计数,不过,若以__block为修饰的物件,参考计数则不受影响
在OC中使用block,以下几个和记忆体管理的事是需要额外注意的
若直接存取实体变量(instance variable)。self的参考计数将加1
若透过变量存取实体变量的值,则只变量的参考计数将加1
以下代码说明上述两个问题,假设instanceVariale是实体变量
dispatch_async(queue,^{
        doSomethingWithObject(instanceVariable); //因为直接存取实体变量,所以self的retain count会加1});
        id localVariable = instanceVariable;
        dispatch_async(queue,^{
            doSomethingWithObject(localVariable);// localVariable是存取值,所以这时只有localVariable的retain count加1 // self 的retain Count并不会增加
        });

五、 使用Block
(一)呼叫一个Block
当block定义成一个变量时,我们可以像使用一般函数的方式来使用它,参考下面两个范例
int(^oneFrom)(int) = ^(int anInt)
    {
        return  anInt-1;
    };
    printf(@"1from10is%d",oneFram(10));//结果会显示: 1from10is9
    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当做参数传入函数,我们通常会使用内嵌的方式来使用Block
char*myCharacters[3]= {"TomJohn","George","Charles Condomine"};
    qsort_b(myCharacters,3,sizeof(char*),
            ^(const void*l,constvoid*r){
                char *left = *(char**)l;
                char *right = *(char **)r;
            };// 这里是Block的终点
    );// 最后的结果为:{“Charles Condomine", "George", "TomJohn"}
在上面的例子中,block本身就是函数参数的一部分,在下一个例子中dispatch_apply函数中使用block 
void dispatch_apply( size_t iterations,dispatch_t queue , void (^block)( size_t ));
这个函数将block提交到发送队列(dispatch queue)中来执行多重的呼叫,只有当队列中的工作都执行完成才会回传,这个函数拥有三个变量,最后一个参数就是block,参考下面的范例
size_t = 10;
    dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_apply(count,queue,^(size_t){
        printf(@"%u\n",i);
    });
(三)将BLock当做方法的参数

我们可以像传递一般参数的方式来传递block,下面示范在一个队列的前5笔资料中取出我们想要的资料的索引值
NSArray *array = [NSArrayarrayWithObjects:@"A" ,@"B" , @"C" ,@"A" , @"B" ,@"Z" , @"G" ,@"are" , @" Q" ,nil];// 所有的资料
    NSSet*filterSet = [NSSetsetWithObjects:@"A" ,@"B" , @"Z" ,@"Q" , nil];//我们只要这个集合内的资料
    BOOL(^test)(id obj,NSInteger idx,BOOL*stop){//只对前5笔做检查
        if(idx <5){
            if[filterSet containsObject:obj]{
                returnYES;
            }}
        return NO;
    };NSIndexSet *indexes = [arrayindexesOfObjectsPassingTest :test];
    NSLog(@"indexes:%@",indexes);
(四)该避免使用的方式
在下面的例子中,block是for循环的局部变量,因此应该避免将局部block指定给外面定义的block
// 这是错误的范例,请勿在程式中使用这些语法!!
     void dontDoThis() {
         void (^blockArray[3])(void);
         // 3 个block的阵列
         for (int i =0; i < 3; ++i)
         {
             blockArray[i] = ^{
                 printf("hello, %d\n", i);
             };
             // 注意:这个block 定义仅在for回圈有效。
         }
     }
     void dontDoThisEither() {
         void (^block)(void);
         int i = random():
         if (i > 1000) {
             block = ^{ printf("got i at: %d\n", i);
             };
         // 注意:这个block 定义仅在if后的两个大括号中有效。
         }
         // ...
     }
    // 结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
    // 前5笔资料中,有4笔符合条件,它们的索引值分别是0-1, 3-4
    int x = 123 ;
    void (^printXAndY)(int ) = ^( int y)
    {
        // 下面这一行是错的,因为x在这是一个常数不能被修改。
        x = x + y;
        printf ( "%d %d\n" , x, y);
        };
若想修改上面的变量x,必须将x加上修饰词__block,参考下一小节
(二) __block型态变量
__block 修饰的变量由只读变成可读可写的,不过有一个限制就是传入的变量在堆中必须占有固定内存的 。无法修饰像是变动长度的阵列这类的变量 // 加上 __block 修饰词,在 __block 中可被修改
 __block int x =123;
    void(^printXAndY)(int) = ^(int y)
    {
        x = x + y;
        printf(@"%d%d",x,y);
    };
    // 将会打印出 579 345
    printXAndY(456);
    // x将会变成579;
    // 下面使用一个范例来介绍各类型的变量和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的内容
        // 执行完后,localCharacter会变成’a’;
    }
    
    char *myCharacters[3 ] = { "TomJohn" ,"George" , "Charles Condomine" };
    qsort_b (myCharacters,3 ,sizeof (char *),^( constvoid *l, constvoid *r)//block部分
    {
        char *left = *(char **)l;
        char *right = *(char **)r;
        return strncmp (left, right, 1 );
    }
             //end
    );
(三)__block 变量
一般来说,在block内只能读取同一个作用域的变量并且没有办法修改block外定义的任何变量。此时如果我们想让这些变量在block中被修改,就必须在前面加上__block修饰词
以第一个例子为例multiplier来说,这个变量在block中是只读的,multiplier只能是7不能修改,若我们想在block中修改multiplier,在编译的时候会报错,必须在multiplier前面加上__block
__block int multiplier =7 ;
     int (^myBlock)( int ) = ^( int num)
     {
         if (num > 5 ){
             multiplier = 7 ;
         }
         else
         {
             multiplier = 10 ;
         }
     return num * multiplier;
     };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值