使用Blocks来进行工作(二)

2017-08-23  本文已影响0人  进击的鸭子

Blocks能够从封闭的空间中拿到值

除了包含可执行的代码,一个block同样有能力区捕捉到封闭空间内的状态。打个比方说如果你将block的申明显示在一个方法里面,在这个封闭的方法内部获得任何值可能就成了一件很容易的事情,像下面这样:

-(void)testMethod {
       int anInteger = 42;

        void (^testBlock)(void) = ^{
                NSLog("@Integer is:%i",anInteger);
        };
        testBlock();
}

在这个例子中,anInteger是一个申明在block外部的变量,但是这个变量的值却可以在block被定义了之后在其内部被捕捉到。这个变量的值只能是被捕捉到的那个值,即便你有其他具体的说明或修改,它还是当时你捉到的那个变量值!这个意味着如果你想改变这个外部变量的值在你定义block和实现这个block这段时间之内,像下面这样:

int anInteger = 42;
void (^testBlock)(void) = ^{
        NSLog(@"Integer is:%i",anInteger);
};
anInteger = 84;
testBlock();

通过上述例子我们可以发现,这个变量的值在block中并没有被影响到,这个意味着日志的输出始终会显示:

Integer is :42

这个同样也说明了block是无法更改原始变量的值,或者甚至是被捕捉到的值(这个被捕捉到的变量值对于block来说就是一个const的变量)

note:你可以尝试在block内部去修改变量值,你发现会报错;
另外,在block定义之前你去改变变量的值是可以的,block定义之后被捕捉到的话,这个值的外部变化对它是没有任何影响的。

利用__block变量来共享内存

如果你有这个需要在block内部将捕捉到的变量值进行修改的需求,你可以通过使用存储类型修饰符__block来修饰原始变量。这个意味着变量生活的内存被分享在原始变量的词汇范围和任何有block声明的范围之间。
举个例子来说,你可能会像这样重写前面提到的例子:

__block int anInteger = 42;

void (^testBlock) (void) = ^{
        NSLog(@"Integer is :%i",anInteger);
};
anInteger = 84;
testBlock();

因为anInteger被申明成一个__block变量,所以它的存储和block申明共享。这也意味着日志的输出将会变成下面这个样子:

Integer is :84

它同样也意味着block可以改变原始变量的值,像下面这样:

__block int anInteger = 42;
void(^testBlock)(void) = ^{
        NSLog("@Integer is :%i",anInteger);
        anInteger = 100;
};
testBlock();
NSLog(@"Value of original variable is now:%i",anInteger);

这一次,日志的输出将会显示:

Integer is :42
Value of original variable is now:100

你可以将Blocks作为方法或函数的参数传递

本章节前面讲的每一个例子都是blocks直接在被定义了之后进行调用。在实际中,blocks作为方法或函数调用中的参数传递也是随处可见。例如,你可能会通过线程:Grand Central Dispatch 在后台调用block,或者去定义一个block去代表重复调用的任务,就比如说当枚举一个集合的时候。

blocks同样被用来作为回调,定义任务完成时要执行的代码。例如,你的app可能现在需要通过创建一个复杂的任务对象去响应一个用户类似请求web网络服务的动作。

这个很有可能通过使用代理来完成:你可能需要去创建一个合适的代理协议,实现被要求使用到的方法,设置这个任务的代理对象,然后等待一旦这个任务结束你创建的代理对象去调用代理方法。

然而,Blocks可以让这些变得更简单,因为你可以定义一个回调行为在你初始化这个任务的时候,像下面这样:

-(IBAction)fetchRemoteInformation:(id)sender {
        [self showProgressIndicator];

        XYZWebTask *task = ....
        
        [task beginTaskWithCallbackBlock:^{
                 [self hideProgressIndicator];
        }];
}

这个例子中,调用了一个方法去显示进度指示器,然后创建并开启一个任务。告诉回调的block在指定任务完成之后要实现的代码。在这个例子中,调用一个方法去隐藏进度指示器看起来是很简单的。但是请注意,block在回调的时候,为了调用hideProgressIndicator方法需要去捕捉self对象。很重要的一点事在捕捉self对象的时候要格外小心。因为我们很容易就会创建一个循环的强引用。这个在之后的介绍中我们会提到。

在代码可读性方面,这个blocks使得我们可以在一个地方就能很容易的看到任务发生的开始和结束,避免了我们需要去追踪代理方法来寻找出任务是怎么发生的。
声明beginTaskWithCallbackBlock:方法可以像下面这个例子一样:

-(void)beginTaskWithCallbackBack:(void(^)(void))callbackBlock;

(void(^)void))说明这个参数是一个没有任何变量或返回值的block,实现方法可以通过一个很常规的途径来调用这个block,像下面这样:

-(void)beginTaskWithCallbackBlock:(void(^)(void))callbackBlock {
        ...
        callbackBlock();
}

使用一个或多个参数的block方法函数也是同样的方式:

-(void)doSomethingWithBlock:(void(^)(double,double))block {
        ...
        block(21.0,2.1);
}
上一篇下一篇

猜你喜欢

热点阅读