背景
block 是 Apple 在 GCC 4.2 中扩充的新语法特性,在iOS4.0+ 和Mac OS X 10.6+ 引进,其目的是支持多核并行编程。可以看到在GCD的实现中,多处使用了block的语法。可以将 dispatch_queue 与 block 结合起来使用,方便进行多线程编程。回调机制中回调设置代码和回调方法的具体实现无法写在同一段代码中。
Block的最大亮点在于它看上去是一段代码,但是可以当作对象来处理。
概念
block的格式定义和各个参数如图所示,block的标志就是^符号。block可以有入参也可以没有,可以有返回值也可以没有。
Block的声明
能够通过名称使用某个Block对象,就必须先声明一个Block对象变量。下面的代码就声明了一个叫做devowelizer的block变量,但是没有对这个block进行赋值。
Block的声明和赋值
block的声明和赋值也可以写在一起。等号后面就是对block进行赋值。
Block的使用
对于有返回值的Block对象,可以像调用函数那样调用Block对象,然后使用其返回值。
double (^divBlock)(double,double) = ^(double k, double j ) {
return k/j;
}
double quotient = divBlock(42.0,12.5);
这段代码首先声明了一个名为divBlock的Block变量,其返回值是k/j的值,入参和返回值均为double类型。quotient直接使用divBlock的返回值。
可以看出,使用Block极大简化了代码的复杂度。
Block使用中的坑
1.修改外部变量的值
在 Block中,可以使用外部变量,但是如果试图修改外部变量的值,就会发现并不生效。如果需要在Block对象内修改某个外部变量,可以在声明相应的外部变量时,在前面加上__block关键字。例如,以下代码可以在Block对象内将外部变量counter的值增1。
_block int counter = 0;
void (^counterBlock)( ) = ^{ counter ++ };
...
counterBlock();//counter增1,数值为2
counterBlock();//counter增1,数值为2
如果这段代码没有使用_block关键字,那么编译器会在 Block 对象的定义处报错,提示修改counter值是无效的。
2.循环引用
Block会使用strong引用使用一个对象,这意味着凡是对Block对象用到的对象都会被retain。因此可能会导致retain循环问题。如果一个对象中声明了一个Block,而Block中又使用了这个对象,就会发生retain循环(两个对象互相持有导致无法释放就称为循环引用)。
@implementation LXDObject
{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad
{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self); //引发循环引用
};
}
@end
解决途径:先在 Block对象外声明一个__weak指针,然后将这个指针指向Block 对象使用的对象,最后在 Block 对象中使用这个新的指针。
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf); //弱指针引用,不会造成循环引用
};
使用弱指针引用后有可能出现新的问题,在后面想要使用弱指针指向的对象时,其已经被释放。为了避免这种情况,可以再在block中对其进行强引用。
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
__strong __typeof(weakSelf)strongSelf = weakSelf; //强引用避免对象提前释放
NSLog(@"%@", strongSelf);
};