block(2)

第三章 声明和创建Blocks 3.1 声明一个block的引用

Block 变量拥有 blocks 的引用。你可以使用和声明函数指针类似的语法来声明它 们,除了它们使用 ^修饰符来替代 * 修饰符。Block 类型可以完全操作其他 C 系统 类型。以下都是合法的 block声明: 

    void (^blockReturningVoidWithVoidArgument)(void);

    int (^blockReturningIntWithIntAndCharArguments)(intchar);

    void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

 

Blocks 还支持可变参数(...)。一个没有使用任何参数的 block 必须在参数列表 上面用 void 标明。

Blocks 被设计为类型安全的,它通过给编译器完整的元数据来合法使用 blocks、 传递到 blocks 的参数和分配的返回值。你可以把一个 block 引用强制转换为任意类型 的指针,反之亦然。但是你不能通过修饰符 * 来解引用一个 block,因此一个 block 的大小是无法在编译的时候计算的。

你同样可以创建 blocks 的类型。当你在多个地方使用同一个给定的签名的 block 时,这通常被认为是最佳的办法。 

    typedef float (^MyBlockType)(floatfloat);

    MyBlockType myFirstBlock = // ... ;

    MyBlockType mySecondBlock = // ... ;

 

3.2 创建一个block

你可以使用 ^ 修饰符来标识一个 block 表达式的开始。它通常后面跟着一个被 () 包含起来的参数列表。Block 的主体一般被包含在 {} 里面。下面的示例定义了一个 简单的 block,并把它赋值给前面声明的变量(oneFrom)。这里 block 使用一个标准 C 的结束符 ; 来结束。

    int (^oneFrom)(int);

    oneFrom = ^(int anInt) {

        return anInt - 1;

    }; 

如果你没有显式的给 block 表达式声明一个返回值,它会自动的从 block 内容推 断出来。如果返回值是推断的,而且参数列表也是 void,那么你同样可以省略参数列 表的 void。如果或者当出现多个返回状态的时候,它们必须是完全匹配的(如果有必 要可以使用强制转换)。

3.3 全局blocks

在文件级别,你可以把 block 作为全局标示符:

#import

    

    

    int GlobalInt = 0;

    int (^getGlobalInt)(void) = ^{ return GlobalInt; };

第四章 Blocks和变量 

本文描述 blocks 和变量之间的交互,包括内存管理。

你可以引用三种标准类型的变量,就像你在函数里面引用那样:

  •  全局变量,包括静态局部变量。

  •  全局函数(在技术上而言这不是变量)。

  •  封闭范围内的局部变量和参数。

    Blocks 同样支持其他两种类型的变量:

  1. 在函数级别是__block变量。这些在block里面是可变的(和封闭范围),并任何引

     block 的都被保存一份副本到堆里面。

  2. 引入const

  3. 最后,在实现方法里面,blocks也许会引用Objective-C的实例变量。参阅“对象

     Block 变量”部分。

     block 里面使用变量遵循以下规则:

  1. 全局变量可访问,包括在相同作用域范围内的静态变量。

  2. 传递给block的参数可访问(和函数的参数一样)。

  3. 程序里面属于同一作用域范围的堆(非静态的)变量作为const变量(即只读)。

    它们的值在程序里面的 block 表达式内使用。在嵌套 block 里面,该值在最近的

    封闭范围内被捕获。

  4. 属于同一作用域范围内并被__block存储修饰符标识的变量作为引用传递因此是

    可变的。

  5. 属于同一作用域范围内block的变量,就和函数的局部变量操作一样。

    每次调用 block 都提供了变量的一个拷贝。这些变量可以作为 const 来使用,或在 block 封闭范围内作为引用变量。 

下面的例子演示了使用本地非静态变量:

    int x = 123;

    void (^printXAndY)(int) = ^(int y) {

        printf("%d %d\n", x, y); 

    };

    printXAndY(456); // prints: 123 456

正如上面提到的,在 block 内试图给 x 赋一个新值会导致错误发生:

    int x = 123;

    void (^printXAndY)(int) = ^(int y) {

        x = x + y; // error

        printf("%d %d\n", x, y);

    };

为了可以在 block 内修改一个变量,你需要使用__block 存储类型修饰符来标识该 变量。参阅“__block 存储类型”部分。

4.2 __block存储类型

你可以指定引入一个变量为可更改的,即读-写的,通过应用__block 存储类型修 饰符。局部变量的__block 的存储和 registerautostatic 等存储类型相似,但它们之 间不兼容。

__block 变量保存在变量共享的作用域范围内,所有的 blocks  block 副本都声明 或创建在和变量的作用域相同范围内。所以,如果任何 blocks 副本声明在栈内并未超 出栈的结束时,该存储会让栈帧免于被破坏(比如封装为以后执行)。同一作用域范 围内给定的多个 block 可以同时使用一个共享变量。

作为一种优化,block存储在栈上面,就像blocks本身一样。如果使用Block_copy 拷贝了 block 的一个副本(或者在 Objective-C 里面给 block 发送了一条 copy 消息), 变量会被拷贝到堆上面。所以一个__block 变量的地址可以随时间推移而被更改。

使用__block 的变量有两个限制:它们不能是可变长的数组,并且它们不能是包 含有 C99 可变长度的数组变量的数据结构。

以下举例说明了如何使用__block 变量:

    __block int x = 123; // x lives in block storage

    void (^printXAndY)(int) = ^(int y) {

    

        x = x + y;

        printf("%d %d\n", x, y);

    };

    printXAndY(456); // prints: 579 456

    // x is now 579 


下面的例子显示了 blocks 和其他几个类型变量间的交互:

 extern NSInteger CounterGlobal;

 static  NSInteger CounterStatic;

    

    {

        NSInteger localCounter = 42;

        __block char localCharacter;

        void (^aBlock)(void) = ^(void) {


            ++CounterGlobal;

            ++CounterStatic;

            CounterGlobal = localCounter; // localCounter fixed at block creation

                     localCharacter = 'a'; // sets localCharacter in enclosing scope

            

        };

        ++localCounter; // unseen by the block

        localCharacter = 'b';

aBlock(); // execute the block

// localCharacter now 'a'

} 

 

4.3 对象(Object)Block变量
Block 提供了支持 Objective-C  Objective-C++的对象,和其他 blocks 的变量。

4.3.1 Objective-C对象

在引用计数的环境里面,默认情况下当你在 block 里面引用一个 Objective-C 对象的时 候,该对象会被 retain。当你简单的引用了一个对象的实例变量时,它同样被 retain。 但是被__block 存储类型修饰符标记的对象变量不会被 retain. 注意:在垃圾回收机制里面,如果你同时使用__weak 和__block 来标识一个变量,那么该 block 将不会保证它是一直是有效的。

如果你在实现方法的时候使用了 block,对象的内存管理规则更微妙:

如果你通过引用来访问一个实例变量,self 会被 retain
如果你通过值来访问一个实例变量,那么变量会被 retain

下面举例说明两个方式的不同:

   dispatch_async(queue, ^{

        

        // instanceVariable is used by reference, self is retained

        doSomethingWithObject(instanceVariable);

        

    });

    

    

    

    id localVariable = instanceVariable;

    

    dispatch_async(queue, ^{

        

        // localVariable is used by value, localVariable is retained (not self)

        

        doSomethingWithObject(localVariable);

        

 

4.3.2 C++对象

通常你可以在 block 内使用 C++的对象。在成员函数里面,通过隐式的导入 this 指针引用成员变量和函数,结果会很微妙。有两个条件可以让 block 被拷贝: 

如果你拥有__block存储的类,它本来是一个基于栈的C++对象,那么通常会使用 copy 的构造函数。

如果你在 block 里面使用任何其他 C++基于栈的对象,它必须包含一个 const copy 的构造函数。该 C++对象使用该构造函数来拷贝。

4.3.3 Blocks

当你拷贝一个 block 时,任何在该 block 里面对其他 blocks 的引用都会在需要的 时候被拷贝,即拷贝整个目录树(从顶部开始)。如果你有 block 变量并在该 block 里 面引用其他的 block,那么那个其他的 block 会被拷贝一份。

当你拷贝一个基于栈的 block 时,你会获得一个新的 block。但是如果你拷贝一个 基于堆的 block,你只是简单的递增了该 block 的引用数,并把原始的 block 作为函数 或方法的返回值。


五章 使用Blocks

5.1 调用一个Block
如果你声明了一个 block 作为变量,你可以把它作为一个函数来使用,如下面的

两个例子所示: 

int (^oneFrom)(int) = ^(int anInt) {

        

        return anInt - 1;

        

    };

    

    printf("1 from 10 is %d", oneFrom(10));

    

    // Prints "1 from 10 is 9"

    

    float (^distanceTraveled) (floatfloatfloat) =

    

    ^(float startingSpeed, float acceleration, float time) {

        

        

        float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);

        

        return distance;

        

    };

    

    float howFar = distanceTraveled(0.09.81.0);

    // howFar = 4.9

然而你通常会把 block 作为参数传递给一个函数或方法。在这种情况下,你通过 需要创建一个”内联(inline)”的 block

5.2 使用Block作为函数的参数

你可以把一个 block 作为函数的参数就像其他任何参数那样。然而在很多情况下, 你不需要声明blocks;相反你只要简单在需要它们作为参数的地方内联实现它们。下 面的例子使用 qsort_b 函数。qsort_b 和标准 qsort_r 函数类似,但是它最后一个参数用 block. 

    char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };

    

    

    qsort_b(myCharacters, 3sizeof(char *), ^(const void *l, const void *r) {

        

        char *left = *(char **)l;

        

        char *right = *(char **)r;

        

        return strncmp(left, right, 1);

        

    });

    

    // Block implementation ends at "}"

    

    

    // myCharacters is now { "Charles Condomine", "George", "TomJohn" }

 

注意函数参数列表包含了一个 block

下一个例子显示了如何在 dispatch_apply 函数里面使用 blockdispatch_apply 声明 如下: 

    void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

 

该函数提交一个 block 给批处理队列来多次调用。它需要三个参数;第一个指定 迭代器的数量;第二个指定一个要提交 block 的队列;第三个是 block 它本身,它自 己需要一个参数(当前迭代器的下标)。

你可以使用 dispatch_apply 来简单的打印出迭代器的下标,如下:


#include

    

    size_t count = 10;

    

    dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    

    

    dispatch_apply(count, queue, ^(size_t i) {

        

        printf("%u\n", i);

        

    }); 


5.3 使用Block作为方法的参数

Cocoa 提供了一系列使用 block 的方法。你可以把一个 block 作为方法的参数就像 其他参数那样。

下面的例子确定数组前面五个元素中第一个出现在给定的过滤器集中任何一个 的下标。

   

    

    NSArray *array = [NSArray arrayWithObjects: @"A"@"B"@"C"@"A"@"B",@"Z",@"G"@"are"@"Q"nil];

    

    

    NSSet *filterSet = [NSSet setWithObjects: @"A"@"Z"@"Q"nil];

    

    

    BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);

    

    

    test = ^ (id obj, NSUInteger idx, BOOL *stop) {

        

        

        if (idx < 5) {

            

            if ([filterSet containsObject: obj]) {

                

                return YES;

                

            }

            

        }

        

        return NO;

        

    };

    

    

    NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];

    

    

    NSLog(@"indexes: %@", indexes);

    

    

     

下面的例子确定一个 NSSet 是否包含一个由局部变量指定的单词,并且如果条件 成立把另外一个局部变量(found)设置为 YES(并停止搜索)。注意到 found 同时被声 明为__block 变量,并且该block 是内联定义的:

__block BOOL found = NO;

    

    NSSet *aSet = [NSSet setWithObjects: @"Alpha"@"Beta"@"Gamma"@"X"nil];

    

    NSString *string = @"gamma";

    

    [aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {

        

        if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {

            

            *stop = YES;

            

            found = YES;

            

        }

        

    }];

    

    // At this point, found == YES


5.4 拷贝Blocks

通常,你不需要 copy(或 retain)一个 block.在你希望 block 在它被声明的作用域 被销毁后继续使用的话,你子需要做一份拷贝。拷贝会把 block 移到堆里面。

你可以使用 C 函数来 copy  release 一个 block:

  

   Block_copy();

  Block_release();

如果你使用 Objective-C,你可以给一个 block 发送 copyretain  release(或 autorelease)消息。

为了避免内存泄露,你必须总是平衡 Block_copy() Block_release()。你必须平衡 copy  retain release(或 autorelease)--除非是在垃圾回收的环境里面。

5.5 需要避免的模式

一个 block 的文本(通常是^{...})是一个代表 block 的本地栈数据结构地址。 因此该本地栈数据结构的作用范围是封闭的复合状态,所以你应该避免下面例子显示 的模式:

void dontDoThis() {

        

        void (^blockArray[3])(void); // an array of 3 block references

        

        

        for (int i = 0; i < 3; ++i) {

            

            blockArray[i] = ^{ printf("hello, %d\n", i); };

            

            // WRONG: The block literal scope is the "for" loop

            

        }

        

    }

    void dontDoThisEither() {

        

        void (^block)(void);

        

        

        int i = random():

        

        if (i > 1000) {

            

            block = ^{ printf("got i at: %d\n", i); };

            

            // WRONG: The block literal scope is the "then" clause

            

        }

        

        // ...

        }

5.6 调试
你可以在 blocks 里面设置断点并单步调试。你可以在一个 GDB 的对话里面使用

invoke-block 来调用一个 block。如下面的例子所示:

    $ invoke-block myBlock 10 20

如果你想传递一个 C 字符串,你必须用引号括这它。例如,为了传递 this string doSomethingWithString  block,你可以类似下面这样写:

$ invoke-block doSomethingWithString ""this string""    

结束语

Block  iOS 4.0 之后添加的新特性支持。本人亲测感觉使用 Block 最大的便利就 是简化的回调过程。以前使用 UIView 的动画,进程要控制动画结束后进行相应的处 理。iOS 4.0 之后,UIView新增了对 Block 的支持,现在只要使用简单的一个 Block 代码就可以在写动画的代码部分直接添加动画结束后的操作。还有就是在使用Notification 时候 Block 也非常有帮助。反正多用就可以体会到 Block 的优美了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值