前言
做iOS开发也有一阵子了,项目中使用了大量的 block —这个从 iOS 4开始支持的语法,偶是从 iOS 6 开始做开发滴,因此上来就开始使用了,这玩意就是别的语言的闭包!
对 block 的初步理解
我一直把 block 作为一个匿名函数来理解,很多地方都跟函数很相似,更确切的说他本质上几乎就是个函数指针,他的生命周期跟函数一样,正因此如此,所以把 block 描述为属性时,我们都会使用 copy,目的在于将它从栈上拷贝到堆上!这样就能保证回调的时候能找到他,而不是早已经被释放掉了;既然使用了 copy,那么你就有义务管理好这块内存滴啦,可以使用 Block_release(aBlock),不过对于描述为属性的 block ,一般都是 self.theBlock = nil; 就行了!
下面简单谈下 block 的捕获吧,所谓捕获就是在block 块内部使用到的变量会被捕捉到快内,可以直接使用,为了安全起见,block 会对捕获的对象 retain,也正因为这个原因,才使得有的时候我们不敢使用 block,因为这很容易引起循环引用问题!下面简单介绍下捕获:
-
捕获一个局部的基本数据类型时(int ,float …):
block 会把这个局部变量 copy 一份,这个 copy 的过程是值得赋值,并且是 const 的,也就是说是只读的!若想在 block 块内改变需要使用 __block 修饰,这会改变 copy 的过程,copy 的是这个变量的地址,相当于 c 语言的地址传递,因此可以做到在 block 快内改变外部变量的效果! 举例说明:__block int a = 100; void (^test)(void) = ^(){ a++; NSLog(@"--in block :a = %d",a); }; NSLog(@"--before inkove block :a = %d",a); //--before inkove block :a = 100 test(); //--in block :a = 101 NSLog(@"--after inkove block :a = %d",a); //--after inkove block :a = 101
-
捕获全局的基本数据类型时,则不需要使用 __block 修饰就可以修改,并且修改之后直接影响该全局变量;举例说明:
int g; int main(int argc, const char * argv[]) { @autoreleasepool { __block int a = 100; void (^test)(void) = ^(){ a++; g++; NSLog(@"--in block :a = %d",a); }; NSLog(@"--before inkove block :a = %d,g = %d",a,g); //--before inkove block :a = 100,g = 0 test(); //--in block :a = 101,g = 1 NSLog(@"--after inkove block :a = %d,g = %d",a,g); //--after inkove block :a = 101,g = 1 } return 0; }
-
捕获一个 oc 对象时(NSObject)要分情况:
-
当 block 是局部的,那么:
对于一个 oc 对象(不管是全局的还是局部的)而言,block 都不会保留这个对象,使用完毕当然也不会释放;原因是:block 的内存分配在了栈上,使用完毕就收回内存空间了,而他要捕获的 oc 对象的生命周期根本不小于它,所以它对 oc 对象作 retain 操作也没有意义,只会带来更多内存管理的麻烦!举例说明:@implementation Maker - (void)dealloc { NSLog(@"%@:%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd)); [super dealloc]; } @end //test maker Maker *obj = [[Maker alloc]init]; obj.m = 123; obj.num = @(999); // [obj release]; 做了个实验,即使这里释放掉,block 块内仍然可以正常执行!不知为何!!! void (^test)(void) = ^(){ obj.m = 100; obj.num = @(888); NSLog(@"--in block :num=%@",(unsigned long)[obj retainCount],[obj num]); }; NSLog(@"--before inkove block :obj = %lu,num=%@",(unsigned long)[obj retainCount],[obj num]); test(); NSLog(@"--after inkove block :obj = %lu,num=%@",[obj retainCount],[obj num]); [obj release]; //Maker:dealloc
通过观察 retainCount,发现调用这个局部 block 前后 maker 的引用计数并没有改变!令我惊奇的是即使在 block 上面就 release maker ,并且 maker 的 dealloc 也调用了,block 里面照样能够打印出正确的结果来!这使我很郁闷!
-
当 block 是全局的:
Block会对OC对象retain!并且这个操作是不负责的,因为它没有做出相应的release操作!这也是使用Block引起循环引用的原因!另外,self也要理解为局部的,如果使用了self,那么self的引用计数就会加1!需要明确一点,只是self的引用计数变了,而self的成员变量的引用计数并没有改变!
一般都会使用__Block 弱化这个指针来解决,ARC中使用__Week来解决!
-
把 block 作为参数
系统的其实跟自己写的一样,比如UIView的Block方式开启动画:
[UIView animateWithDuration:3 animations:^{
NSLog(@”动画开始:self’s retainCount is %d”,self.retainCount);
// NSLog(@”arr’s retainCount is %d”,this.arr.retainCount);
NSLog(@”tempArr’s retainCount is %d”,tempArr.retainCount);
}completion:^(BOOL finished) {
}];
这里的self并没有被retain;
下面的代码就不同了:
[UIView animateWithDuration:3 animations:^{
NSLog(@”动画开始:self’s retainCount is %d”,self.retainCount);
// NSLog(@”arr’s retainCount is %d”,this.arr.retainCount);
NSLog(@”tempArr’s retainCount is %d”,tempArr.retainCount);
}completion:^(BOOL finished) {
NSLog(@"动画结束:self's retainCount is %d",<span style="color:#990000;">self</span>.retainCount);
}];
区别是在completion方法里使用了self,它就被retain了!
想了一下这里还是可以理解的,因为这个动画不知道被指定多长时间,所以系统就不得不把这个Block 拷贝到堆上,那么这样一来Block的生命周期就长了,为了回调时能够找到对象,所以就retain了,从而保证了在访问对象时,对象的安全存在性!很遗憾,系统又是不分责任的retain,所以又打乱了内存管理原则!你或许应该使用__Block去弱化这个指针!
平时会遇到很多类似的Block,网络请求就是其中之一,他们的共性都是处理完成之后的事情的,其实并不神秘,只不过Block被拷贝到堆上了而已。。。
弱化指针我一般都是这么写的:
__block typeof(self)this = self;
《Block拷贝也有深浅之说》
如果Block已经在堆上了,那么再次对Block拷贝就是浅拷贝,并不会真的再次拷贝一份到堆上,比如:
void (^block)(void) = ^{ printf(“Block”);};
void (^blockHeap1)(void) = [block copy];//blockHeap1在堆中,这是深拷贝
void (^blockHeap2)(void) = [blockHeap1 copy];//blockHeap2也在堆中,而且是浅拷贝 …