一 、Block的内存泄露体现
block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。
一般表现为:
1.某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是
self.someBlock = ^(Type var){
[self dosomething];
self.otherVar = XXX;
或者_otherVar = ...
};
block的这种循环引用会被编译器捕捉到并及时提醒。(这个还是很人性化的,难道你不这么觉得吗?0.0)
list:
// Created by 58 on 15/6/3.
// Copyright (c) 2015年 58. All rights reserved.
//
#import"TsetBlock.h"
void (^blockTest)(NSString *str,NSString *st );
typedefvoid (^blockT)(NSString *str,NSString *st );
@interfaceTsetBlock ()
@property (nonatomic)NSArray *testArr;
@property (nonatomic ,copy)blockT block;
@end
@implementation TsetBlock
-(id)init{
if (self = [superinit]) {
self.testArr =@[@"你",@"觉",@"的",@"会",@"怎",@"样"];
self.block = ^(NSString *name,NSString *str){
NSLog(@"arr:%@",self.testArr);
};
}
returnself;
}
@end
很明显了:
即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!但对于这种情况,目前我不知道该如何排除掉循环引用,因为我们无法通过加__weak声明或者__block声明去禁止block对self进行强引用或者强制增加引用计数。对于self.arr的情况,我们要分两种环境去解决:
1.arc: __weaktypeof(self) weakSelf=self; 其实 __weak someClass *weakSelf = self也是OK的!!!
2.MRC:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:孙子,咱们已经没有关系了(不要在内部对self进行retain了)!
二、正确的使用BLOCK避免cycle retain
我们一起来看看,经Clang编译后的block结构
struct Block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved;
unsigned long int size;
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2014.5.25
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
};
Block执行时需要的所有外部变量值
的数据结构。 Block将使用到的、作用域附近到的变量的值
建立一份快照拷贝到栈上。_NSConcreteStackBlock
或者_NSConcreteGlobalBlock
类的地址。在没有开启ARC的情况下,如果Block中包含有局部变量则isa被初始化为前者,否则就被初始化为后者。而当ARC开启后,如果Block中包含有局部变量则isa被初始化为_NSConcreteMallocBlock
,否则就被初始化为_NSConcreteGlobalBlock
。invoke是一个函数指针,它指向的是Block被转换成函数的地址。最后的imported variables部分是Block需要访问的外部的局部变量,他们在编译就会被拷贝到Block中,这样一来Block就是成为一个闭包了。1> blockT blk1 = ^ int(int a, int b) {
return a+b ;
};
(lldb)po blk1
<__NSGlobalBlock__: 0x102b54080>
2> int base =100;
blockT blk2 = ^ int(int a,int b) {
return base +a+b ;
};
(lldb)po blk2
<__NSStackBlock__: 0x7fff5d0abab0>
3> blockT blk3 = [[blk2copy] autorelease];
(lldb)po blk3
<__NSMallocBlock__: 0x7f89aa6854f0>
在Block内变量base是只读的,如果想在Block内改变base的值,在定义base时要用__block
修饰[mutableAarry addObject:stackBlock]
,在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]
。支持copy,copy之后生成新的NSMallocBlock类型对象。
|
|
|
|
request
被持有者释放后。request 的retainCount变成0,request被dealloc,request释放持有的Block,导致Block的retainCount变成0,也被销毁。这样这两个对象内存都被回收。retain cycle不只发生在两个对象之间,也可能发生在多个对象之间,这样问题更复杂,更难发现
|
|
|
|
解决办法同样是用__block
打破循环引用
|
|
注意:MRC中
__block
是不会引起retain;但在ARC中__block
则会引起retain。ARC中应该使用__weak
或__unsafe_unretained
弱引用。__weak
只能在iOS5以后使用。
看下面例子,有这种情况,如果不只是request
持有了Block,另一个对象也持有了Block。
|
|
这时如果request 被持有者释放。
|
|
这时request已被完全释放,但Block仍被objA持有,没有释放,如果这时触发了Block,在Block中将访问已经销毁的request,这将导致程序crash。为了避免这种情况,开发者必须要注意对象和Block的生命周期。
另一个常见错误使用是,开发者担心retain cycle错误的使用__block
。比如
|
|
将Block作为参数传给dispatch_async时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为dispatch_async并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后再release self。但这里使用__block
,使dispatch_async没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash。
|
|
这里用dispatch_after模拟了一个异步任务,10秒后执行Block。但执行Block的时候MyClass* obj
已经被释放了,导致crash。解决办法是不要使用__block
。