【Objective-C】Block介绍

本文部分内容摘自

1.Block是啥?凭什么这么屌?

就本质来说,一个Block就是一大堆在接下来某个时间可以被执行的代码。

Block 是一等函数(first-class function)[1]。一等函数!这个梦幻般的爵位(如果计算机科学也有爵位的话~),却揭露了它丫的就是Objective-C的一个常规对象。因为它也就是个对象,所以它可以作为参数被传来传去,被方法和函数作为返回值送人,也可以被指定为变量。

Block在一些诸如Python、Ruby、Lisp这样的语言中被叫做闭包,因为在它被声明的时候,它肚子里就已然有货了。一个block会为它范围内的任一变量做个copy,让小弟吃饱穿暖,别稀里糊涂的生命周期就结束了。

在这货出现之前,你想完成回调,最典型的就是用delegate或者NSNotificationCenter。这两个家伙表现挺好的,不过,却让你的代码到处跑,你从一个地方开始任务,却要在另一个地方处理结果。

Block就机灵得多,在处理任务的时候,它让你不挪窝,就把事儿给办了,就像你接下来将要看到的。

2.Block为谁而生?

就是你们!不骗你们,Block是位每一个苦逼程序猿创造的,每个人都该用。Block代表了未来,所以你现在就得学。很多内置框架都基于这货重写或扩展了。

3.Block怎么用?

下面那张妖里妖气的图片,来自苹果开发者库,做了很好的Block句法解释。

《【Objective-C】Block介绍》 block

我从左到右,从上到下翻译一下:

  • 我们声明了一个叫“myBlock”的变量,“^”告诉我们这是一个block;
  • 这是一个字面量block的定义,指派给了“myBlock”;
  • “myBlock”是一个返回整型值的block;
  • 它只有一个整型参数;
  • 参数名字叫“num”;
  • 这是block的货,也就是block的实现。
    Block的声明形式如下:
return_type (^block_name)(param_type, param_type, ...)

如果你有C系语言的编程经验的话,这会让你似曾相识,除了^,^表明“这货正在声明一个block”。

如果你脑海中萦绕着:^表示“我是一个block”,恭喜——你已经学会使用block最难的部分。(我读书少,你可不要骗我啊…)

注意,在声明的时候,给参数命名不是必要的(只要知道是什么类型就行了),如果你愿意可以加上。

以下是声明一个block的例子:

int (^add)(int,int)  

接着,是block的定义:

// Block Definition
^return_type(param_type param_name, param_type param_name, ...) { ... return return_type; }

这就是Block的创建形式。要注意,Block的定义和声明有不同的结构。定义是以^开头,后面紧跟着参数,这次参数要有名字啦,并且它们的顺序要和Block定义中的一样。

当定义Block的时候,返回值类型可以不填,因为它可以根据代码推断出来。如果有多个返回的语句,那每个语句返回的必须是同一类型的(或者强转成相同的类型)。

以下是一个Block定义的例子:


^(int number1, int number2){ return number1+number2 }

如果我们把Block声明和定义的例子组合在一块儿,我们得到一个完整的语句:

int (^add)(int,int) = ^(int number1, int number2){ 
                            return number1+number2;
}  

然后我们可以这样使用:

int resultFromBlock = add(2,2);  

接下来,我们让用了Block的,和没用Block的代码PK一下!

例子:NSArray

我们看看Block怎样改变了我们操作数组。
首先,我们看一个常规的循环:

BOOL stop;
for (int i = 0 ; i < [theArray count] ; i++) {
    NSLog(@"The object at index %d is %@",i,[theArray objectAtIndex:i]);
    if (stop)
        break;
}  

上面方法的stop变量可能让你觉得没什么意义。但是,当你看到同一方法但基于Block,它将变得更明晰。Block方式提供一个stop变量,使你能够随时停止循环。我们刚才简单复制了一些与Block功能相同的代码。
现在我们再看一个用快速枚举的代码:

BOOL stop;
int idx = 0;
for (id obj in theArray) {
    NSLog(@"The object at index %d is %@",idx,obj);
    if (stop)
        break;
    idx++;
}  

最后我们用Block:

[theArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
    NSLog(@"The object at index %d is %@",idx,obj);
}];  

在上面的Block中,你可能好奇stop变量是干嘛的。它是一个简单变量,可以在Block中被置为YES来停止进一步的处理。

上面的例子有点不知所云,而且很难看出Block有嘛好处(擦~,那你为什么这么写…)。但是,有两点是我要指出的:

  • 简单粗暴。用Block,我们有机会操作数组里的对象,对象的索引,和一个布尔变量,不用谢任何代码。而更少的代码以为着更少的错误。
  • 唯快不破。用Block比快速枚举要有哦轻微的速度上的优势。在此例中,这速度上的优势(可能存在)是如此之小根本没法发觉,但在复杂的案例中,这个优势是很重要的(请看官们停止爆粗口吧,作者写到这里,可能也有点惴惴不安,所以给自己提供了一个证据,来证明自己不是大忽悠…)。

例子:UIView 动画

让我们操作一个UIView的简单动画。这个动画就是:把一个UIView从superview上删掉的时候,让它的透明度变为0,并且x、y各增加50。简单不?

不用Block的方式:

- (void)removeAnimationView:(id)sender {
    [animatingView removeFromSuperview];
}
 
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
 
    [UIView beginAnimations:@"Example" context:nil];
    [UIView setAnimationDuration:5.0];
    [UIView setAnimationDidStopSelector:@selector(removeAnimationView)];
    [animatingView setAlpha:0];
    [animatingView setCenter:CGPointMake(animatingView.center.x+50.0, 
                                         animatingView.center.y+50.0)];
    [UIView commitAnimations];
}

用Block的方式:

  - (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
 
    [UIView animateWithDuration:5.0 
                     animations:^{
                        [animatingView setAlpha:0];
                        [animatingView setCenter:CGPointMake(animatingView.center.x+50.0, 
                                                             animatingView.center.y+50.0)];
                     } 
                     completion:^(BOOL finished) {
                         [animatingView removeFromSuperview];
                     }];
}

观察这两个方法,对我来说,Block有3个突出的优点:

  • 代码简洁。用Block,我们不必声明每一个单独的方法来完成回调。
  • 代码集中。用Block,我们不用在一个地方开始动画,在另一个地方执行回调,所有与动画相关的,都在一起,使代码更加可读可写。
  • 苹果大哥这么说的…这就是一个苹果用Block重写已经存在的功能的例子。现在苹果官方建议每位过渡到基于Block的方法(证据)。

4.Block什么时候用?

(谁说外国人不会说官话,请欣赏,咳咳,难道是华裔)
我认为我能给出的最好的建议是,当你觉得合适的时候,你就该用了(hehe~)。很有可能你还想用你以前的方法,因为代码维护啊、适配啊,你用以前的方法更得心应手。但是每次你都要想想,是否Block能简化你的生活,是否可以用基于Block的方法来代替。然后选择最优的。

当然,你会发现你在将来需要用越来越多的Block,因为很多框架,无论是第三方的,或者苹果自己的,已经用Block写了或者重写了很多方法。所以现在开始就用Block吧,这样在面对未来的时候,你才能算有备而来(一句话,形势比人强,程序猿,看着办吧)。

5.创建自己的Block

Block的好,已经说半天了,你肯定已经迫不及待的想写一些Block了,下面给一些代码片段:

// 这是一个用Block做参数的方法
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
    self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)];
}
 
// 调用上面的方法
- (IBAction)buttonTapped:(id)sender {
    [self doMathWithBlock:^(int a, int b) {
        return a + b;
    }];
}

因为Block就是一个Objective-C的对象,所以你可以把它作为一个属性,以便你再调用。这在回调中相当有用。以下是一个例子:

// 声明一个属性
@property (strong) int (^mathBlock)(int, int); // 不用ARC的话,“strong”改为“copy”
 
// 将方法参数中的block储存到属性,方便以后调用
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
    self.mathBlock = mathBlock;
}
 
// 调用方法
- (IBAction)buttonTapped:(id)sender {
    [self doMathWithBlock:^(int a, int b) {
        return a + b;
    }];
}
 
// 接下来就可以用属性了...
- (IBAction)button2Tapped:(id)sender {
    self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}

最后,你可以用typedef简化一下句法。我们把上面的例子改造一下:

// 用typedef创建一种Block类型
typedef int (^MathBlock)(int, int);
 
// 用此类型定义一个属性
@property (strong) MathBlock mathBlock;
 
// 将方法参数中的block储存到属性,方便以后调用
- (void)doMathWithBlock:(MathBlock) mathBlock {
    self.mathBlock = mathBlock;
}
 
// 调用方法
- (IBAction)buttonTapped:(id)sender {
    [self doMathWithBlock:^(int a, int b) {
        return a + b;
    }];
}
 
// 接下来就可以用属性了...
- (IBAction)button2Tapped:(id)sender {
    self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}

6.BlockDelegateDemo

最后,如果你想比较一下BlockDelegate实现回调的异同,这里有我的一个简单Demo

  1. 在计算机科学中,如果一个编程语言把函数当作一等公民,那么就说它有一等函数。具体来说,该语言支持把函数当作一个参数来传递、可以作为其他函数的返回值来返回、可以把它定义为变量,同时,也可以把它作为一种数据结构来存储。(翻译自维基百科·First-class funcion)

    原文作者:刘大帅
    原文地址: https://www.jianshu.com/p/686a9f5bda8d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞