概念

什么是 Blocks?

  • Blocks 是对 C 语言的扩展,也就是带有局部变量的匿名函数;
  • 它的好处是是使得 Objective-C 可以像 JavaScript 一样进行函数式编程;

Blocks 的一些特点:

  • 块可接受参数,也可返回值。
  • 块可以分配在栈或堆上,也可以是全局的,分配在栈上的块可拷贝到堆里。
  • 和标准的 Objective-C 对象一样,具备引用计数了。

定义块

定义一个块对象基础的语法是:

1
2
3
^{
  // Block implementation here
};

 很显然这是一个匿名函数,就像 JavaScript 一样,也可以像下面一样直接执行:

1
2
3
^{
  // Block implementation here
}();

但是通常我们定义一个 Blocks 时,通常是需要一个名字的:

1
2
3
void (^someBlock)() = ^{
  // Block implementation here
};

这样定义的块可以通过像 C 中的语法一样调用:someBlock()

常见问题

块的强大之处是:

  • 在声明它的范围里。所有变量都可以为其所捕获。这也就是说,那个范围里的全部变量,在块里依然可用。

  • 比如,下面这段代码所定义的块,就使用了块以外的变量:

    1
    2
    3
    4
    5
    6
    
    int additional = 5;
    int (^addBlock)(int a, int b) = ^(int a, int b){
      return a + b + addItional;
    };
    
    int add = addBlock(2, 5); // < add = 12
    

我们也可以为常用的块创建 typedef

1
2
3
4
typedef void (^CompletionHandler)(void);

typedef int (^ComputeHandler)(int a, id b, NSObject *c);
typedef NSObject * (^ComputeHandler)(int a, id b, NSObject *c);

但是 blocks 一般是不可以主动修改局部变量的,需要修改的话则需要添加 __block 关键字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- (void)changeValue {
    __block int value = 0;

    void (^someBlock)(void) = ^{
        NSLog(@"value:%i", value); // value:1
        value = 2;
    };
    value = 1;
    someBlock();
    NSLog(@"value:%i", value); // value:2
}

循环引用:

  • 因为块也是一个对象,如果你在块的代码引用了 self 这个变量,则会导致循环引用。比如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    @interface ViewController ()
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, strong) CompletionHandler handler;
    @end
    
    @implementation ViewController
    
    - (void)blocks {
        self.handler = ^{
            NSLog(@"%@", self.name);
        };
    }
    
    @end
    

    这样的循环引用的代码,是会被 xcode 检测出来的。

  • 我们可以声明一个 __weak 的局部变量解决这个问题:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    @interface ViewController ()
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, strong) CompletionHandler handler;
    @end
    
    @implementation ViewController
    
    - (void)blocks {
        __weak __typeof(self)weakSelf = self;
        self.handler = ^{
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            NSLog(@"%@", strongSelf.name);
        };
    }
    
    @end
    

Objective-C 中,NSArray 提供了一个传入块依次执行的函数,其名为 enumerateObjectsUsingBlock。比如下面是一个遍历数组的示例代码:

1
2
3
4
5
6
7
NSArray *array = @[@0, @1, @2, @3, @4, @5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BooL *stop) {
  if([number compare:@2] == NSOrderedAscending) {
    count++;
  }
}] ;