首先,块与函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一 个范围内的东西。块用“^”
符号来表示,后面跟着一对花括号,括号里面是块的实现代码。 例如,下面就是个简单的块:
^{
//Block implementation here
}
块其实就是个值,而且自有其相关类型。与int
、float
或Objective-C
对象一样,也可以把块赋给变量,然后像使用其他变量那样使用它。块类型的语法与函数指针近似。下面列出的这个块很简单,没有参数,也不返回值:
void (^someBlock) () = A {
//Block implementation here
};
这段代码定义了一个名为someBlock
的变量。由于变量名写在正中间,所以看上去也许 =有点怪,不过一旦理解了语法,很容易就能读懂。块类型的语法结构如下:
return_type (^block_name)(parameters)
我们来举个例子,下面这种写法所定义的块,返回int
值,并且接受两个int
做参数:
int (^addBlock) (int a, int b) = ^(int a, int b){
return a + b;
};
定义好之后,就可以像函数那样使用了。比方说,addBlock
块可以这样用:
int add = addBlock (2, 5) ; //< add = 12
块的强大之处是:在声明它的范围里,所有变量都可以为其所捕获。这也就是说,那个范围里的全部变量,在块里依然可用。比如,下面这段代码所定义的块,就使用了块以外的变量:
int additional = 5;
int (^addBlock) (int a, int b) = ^(int a, int b){
return a + b + additional;
};
int add = addBlock (2, 5); //< add = 12
默认情况下,为块所捕获的变量,是不可以在块里修改的。在本例中,假如块内的代码改动了additional
变量的值,那么编译器就会报错。不过,声明变量的时候可以加上__block
修饰符,这样就可以在块内修改了。
例如:
__block int additional = 5;
int (^addBlock) (int a, int b) = ^(int a, int b){
additional++;
return a + b + additional;
};
int add = addBlock (2, 5); //< add = 13
块的另一个用法是“内联块”(inline block)
,例如:
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++;
}
}];
//count = 2
这段范例代码也演示了“内联块”(inline block)
的用法。传给“numerateObjectsUsingBlock:”
方法的块并未先賦给局部变量,而是直接内联在函数调用里了。
然后我们在声明和使用块的时候,要注意它的作用范围。定义块的时候,其所占的内存区域是分配在栈中的。这就是说,块只在定义它的那个范围内有效。比如下面这个:
void(^block)();
if ( /* some condition */ ){
block = ^{
NSLog(@"Block A");
};
} else {
block = ^{
NSLog(@"Block B");
};
}
block();
定义在if
及else
语句中的两个块都分配在栈内存中。编译器会给每个块分配好栈内存, 然而等离开了相应的范围之后,编译器有可能把分配给块的内存覆写掉。于是,这两个块只 能保证在对应的if
或else
语句范围内有效。这样写出来的代码可以编译,但是运行起来时而正确,时而错误。若编译器未覆写待执行的块,则程序照常运行,若覆写,则程序崩溃。
我们为了解决这个问题可以给块对象发送copy
消息以拷贝之。这样的话,就可以把块从栈复制到堆了。
拷贝后的块,可以在定义它的那个范围之外使用。而且,一旦复制到堆上,块就成了带引用计数的对象了。后续的复制操作都不会真的执行复制,只是递增块对象的引用计数。如果不再使用这个块,那就应将其释放,在ARC环境下会自动释放。
改动后跟下面一样:
void (^block)();
if (/* some condition */ ){
block = [^{
NSLog(@,fBlock Aw);
} copy];
} else {
block = [^{
NSLog(@"Block B");
} copy];
}
block();
除了“桟块”和“堆块”之外,还有一类块叫做“全局块”(global block)。这种块不会捕捉任何状态(比如外围的变量等),运行时也无须有状态来参与。块所使用的整个内存区域,在编译期已经完全确定了,因此,全局块可以声明在全局内存里,而不需要在每次用到 的时候于栈中创建。另外,全局块的拷贝操作是个空操作,因为全局块决不可能为系统所回收。这种块实际上相当于单例。下面就是个全局块:
void (^block)() = ^{
NSLog(@"This is a block");
};
由于运行该块所需的全部信息都能在编译期确定,所以可把它做成全局块。这完全是种优化技术:若把如此简单的块当成复杂的块来处理,那就会在复制及丢弃该块时执行一些无谓的操作。
要点
- 块是
C、C++、Objective-C
中的词法闭包。
- 块可接受参数,也可返回值。
- 块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准的
Objective-C
对象一样,具备引用计数了。