iOS内存管理--Block属性用copy修饰 & 避免循环引用的问题
Posted zzz098zzz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS内存管理--Block属性用copy修饰 & 避免循环引用的问题相关的知识,希望对你有一定的参考价值。
一、Block的类型
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
- NSGlobalBlock:类似函数,位于text段;
- NSStackBlock:位于栈内存,函数返回后Block将无效;
- NSMallocBlock:位于堆内存。
不同于NSObjec的copy、retain、release操作:
- Block_copy与copy等效,Block_release与release等效;
- 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
- NSGlobalBlock:retain、copy、release操作都无效;
- NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableArray中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableArray addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
- NSMallocBlock:支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
- 尽量不要对Block使用retain操作
三、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内存管理--Block属性用copy修饰 & 避免循环引用的问题的主要内容,如果未能解决你的问题,请参考以下文章
iOS内存管理--NSArray与NSMutableArray用copy修饰还是strong