Ziank的技术博客

正确使用BLOCK避免CYCLE RETAIN和CRASH

##Block的简介
Block是Objective-C中的一个对象,类似于C语言中的函数指针。与函数指针不同的时Block可以使用上下文中的变量,而函数指针只可以使用全局的变量和入参。

##Block的使用语法
Block可以命名,也可以直接使用未命名的Block对象,如下面示例,就是直接返回了一个未命名的Block对象:

1
2
3
4
5
6
- (long (^)(int, int)) sumBlock {
int base = 100;
return [[ ^ long (int a, int b) {
return base + a + b;
} copy] autorelease];
}



下面一个例子则是先声明了一种Block对象的类型,再定义对应的Block对象:

1
2
3
4
5
6
7
8
9
typedef long (^BlkSum)(int, int);
- (BlkSum) sumBlock {
int base = 100;
BlkSum blk = ^ long (int a, int b) {
return base + a + b;
}
return [[blk copy] autorelease];
}

##Block中得变量使用
Block中的变量主要是指上下文中得assign/retain/weak等类型的局部变量或成员变量的使用:

1
2
3
4
5
6
7
int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld",sum(1,2));

如上面示例中的使用,就是使用了默认assign类型的局部变量base,结果就等于203。因为Block对象在定义时对上下文中的变量进行了备份,所以base在执行Block时的值为200,输出就是203。因此,在Block的内部不能够对assign类型的变量进行修改,因为修改的时备份的值,对实际变量的值是修改无效的。


下面在定义base的时候加上__block,则返回的值为204。

1
2
3
4
5
6
7
__block int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld",sum(1,2));



原因就是使用block/weak定义的变量,Block在定义的时候并没有进行数值的备份,而是记录了类似于指针的地址,所以在后面执行Block的时候,用的值并不是定义时候获取的备份,而是该变量的实际数值。也因此,可以在Block内部对block/weak修饰的变量进行修改。

##retain cycle(引用嵌套)
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。


举个简单地例子:

1
2
3
4
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];

由于request和Block对象相互之间有强引用的关系,导致最后两者都无法释放。也就是Block对象释放必须先释放request,而request释放又必须先释放Block对象,最后都无法释放。


解决的办法就是在Block对象中对request进行弱引用,这样Block的释放就不依赖于request的释放,最后能够全部释放。

1
2
3
4
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];

##Block使用对象被提前释放
Block使用对象被提前释放主要是由于开发者担心retain cycle错误的使用__block导致。

1
2
3
4
5
6
7
8
9
10
11
12
// MyClass.m
- (void) test {
__block MyClass* weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"%@", weakSelf);
});
// other.m
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];

这里用dispatch_after模拟了一个异步任务,10秒后执行Block。但执行Block的时候MyClass* obj已经被释放了,导致crash。解决办法是不要使用__block。