标签:
一、Block的类型
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
不同于NSObjec的copy、retain、release操作:
三、ARC与非ARC下的block
对于引用了外部变量的Block,如果没有对他进行copy
,他的作用域只会在声明他的函数栈内(类型是__NSStackBlock__
),如果想在非ARC下直接返回此类Block,Xcode会提示编译错误的,如下图:
(Xcode提示Returning block that lives on the local stack)
而在ARC环境下,上述代码会编译通过,因为ARC会自动加入copy
操作。
比如可以在ARC下运行如下代码:
//ARC MyBlock block = func(); NSLog(@"%d", block()); NSLog(@"%@", [block class]);
输出:
123 __NSMallocBlock__
类型是__NSMallocBlock__
,说明Block已经被copy
到了堆中了。
即便把Block用strong修饰,系统也会把block copy到堆中。
例如:@property (nonatomic,strong)void (^SubmitBlock)(GoodsModel *goodsModel,int number);
打印一下block的类型为如下图
当然其实在非ARC下,也可以使上面有错误的函数编译通过。如下代码:
typedef int(^MyBlock)(); MyBlock func() { int i = 123; //非ARC下不要这样!!! MyBlock ret = ^{ return i; }; return ret; }
我们把原来的返回值赋给一个变量,然后再返回这个变量,就可以编译通过了。不过虽然编译通过了,这个返回的Block作用域仍是在函数栈中的,因此一旦函数运行完毕后再使用这个Block很可能会引发BAD_ACCESS错误。
所以在非ARC下,必须把Block复制到堆中才可以在函数外使用Block,如下正确的代码:
typedef int(^MyBlock)(); MyBlock func() { //非ARC int i = 123; return [^{ return i; } copy]; }
我们可以直接通过输出变量的指针,就可以验证Block被copy
后,他所引用的变量被复制到了堆中的情况,如下代码(非ARC下):
//非ARC void func() { int a = 123; __block int b = 123; NSLog(@"%@", @"=== block copy前"); NSLog(@"&a = %p, &b = %p", &a, &b); void(^block)() = ^{ NSLog(@"%@", @"=== Block"); NSLog(@"&a = %p, &b = %p", &a, &b); NSLog(@"a = %d, b = %d", a, b = 456); }; block = [block copy]; block(); NSLog(@"%@", @"=== block copy后"); NSLog(@"&a = %p, &b = %p", &a, &b); NSLog(@"a = %d, b = %d", a, b); [block release]; }
输出:
=== block copy前 &a = 0x7fff5fbff8bc, &b = 0x7fff5fbff8b0 === Block &a = 0x100201048, &b = 0x100201068 a = 123, b = 456 === block copy后 &a = 0x7fff5fbff8bc, &b = 0x100201068 a = 123, b = 456
可以看到,在Block执行中,他所引用的变量a和b都被复制到了堆上。而被标记__block
的变量事实上应该说是被移动到了堆上,因此,当Block执行后,函数栈内访问b的地址会变成堆中的地址。而变量a,仍会指向函数栈内原有的变量a的空间。
四、So,Block属性的声明,首先需要用copy修饰符。Block默认存放在栈中,可能随时被销毁,需要作用域在堆中,所以只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的。
五、循环引用的问题
循环引用是另一个使用Block时常见的问题。为什么会循环引用?因为retain。
因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用,如:
self.myblock = ^{ [self doSomething]; };
下面是对ARC环境做的测试:
- (void)dealloc { NSLog(@"no cycle retain"); } - (id)init { self = [super init]; if (self) { #if TestCycleRetainCase1 //会循环引用 self.myblock = ^{ [self doSomething]; }; #elif TestCycleRetainCase2 //会循环引用 __block TestCycleRetain *weakSelf = self; self.myblock = ^{ [weakSelf doSomething]; }; #elif TestCycleRetainCase3 //不会循环引用 __weak TestCycleRetain *weakSelf = self; self.myblock = ^{ [weakSelf doSomething]; }; #elif TestCycleRetainCase4 //不会循环引用 __unsafe_unretained TestCycleRetain *weakSelf = self; self.myblock = ^{ [weakSelf doSomething]; }; #endif NSLog(@"myblock is %@", self.myblock); } return self; } - (void)doSomething { NSLog(@"do Something"); } int main(int argc, char *argv[]) { @autoreleasepool { TestCycleRetain* obj = [[TestCycleRetain alloc] init]; obj = nil; return 0; } }
经过上面的测试发现,在加了__weak和__unsafe_unretained的变量引入后,TestCycleRetain方法可以正常执行dealloc方法,而不转换和用__block转换的变量都会引起循环引用。
因此防止循环引用的方法如下: __weak TestCycleRetain *weakSelf = self;
所以,在ARC下,由于__block抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置空。示例代码:
//iOS 5之前可以用__unsafe_unretained //__unsafe_unretained typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self; self.myBlock = ^(int paramInt) { //使用weakSelf访问self成员 [weakSelf anotherFunc]; };
在非ARC下,显然无法使用弱引用,这里就可以直接使用__block来修饰变量,它不会被Block所retain的,参考代码:
//非ARC __block typeof(self) weakSelf = self; self.myBlock = ^(int paramInt) { //使用weakSelf访问self成员 [weakSelf anotherFunc]; };
六、__weak 和 __block的区别
1. 循环引用时:有上文可知,__weak用在ARC环境时;__block仅可用在非ARC环境时(因为ARC环境下仍然会被retain)。
2. 修改局部变量时:需要加__block,否则不能在block中修改局部变量。如下:
__block int multiplier = 7; int (^myBlock)(int) = ^(int num) { multiplier ++;//这样就可以了 return num * multiplier; };
iOS内存管理(4)--Block属性用copy修饰 & 避免循环引用的问题
标签:
原文地址:http://blog.csdn.net/winzlee/article/details/51744754