参考书籍:《Effective Objective-C 2.0》 《Objective-C高级编程 iOS与OS X多线程和内存管理》
Block其实有种前端中闭包的感觉,语法相对复杂,但在Objective-C中,block可谓是使用的非常广泛,也很有实际用途,本文简介一下block的语法,使用技巧与使用block时经常可能导致的两种内存泄漏。
1.block语法
返回类型 (^blockName) (参数1,参数2...)
其中blockName与参数列表均可省略
example:
int (^blk)(int) = ^(int i){
return 0;
};
2.block使用小技巧
使用tyepdef来为block取别名,为block取了名字后,以后如果对此block对象进行参数的修改或其他修改时,只需在typedef的地方修改,然后编译器便会对所有使用此block的地方报错,方便我们不遗漏的修改到每一处。还有一个好处是对于完全相同的block可以用这种方式明了用途上的不同,方便维护。
example:
typedef void (^MyBlock)(int i);
MyBlock blk = ^(int i){
NSLog(@"MyBlock");
}
blk(2);
3.两种内存泄漏
情况一:
a.example:
typedef void (^TestBlock)();
@interface ZYTest ()
{
NSString *test;
TestBlock blk;
}
@end
@implementation ZYTest
- (instancetype)init{
self = [super init];
if (self) {
test = @"1111";
blk = ^(){
NSLog(@"test = %@",test);
};
blk();
}
return self;
}
此种情况是block为self的变量,为self所持有,但在block中又引用了test变量,即引用了self,所以造成循环引用,这种内存泄漏的情况编译器会给出
的警告,比较好定位,使用instrument来分析,则会看到下图所示的leak
这种内存泄漏的解决方法即打破强引用循环,将block中对self属性的引用声明为weak,如修改上面的代码如下:
- (instancetype)init{
self = [super init];
if (self) {
_test = @"1111";
id __weak temp = _test;
blk = ^(){
NSLog(@"test = %@",temp);
};
blk();
}
return self;
}
情况二:
类:
typedef void (^TestBlockWithData)(NSString *str);
#import "ZYTest.h"
@interface ZYTest ()
@property (nonatomic,strong)NSString *finishData;
//自己的block变量
@property (nonatomic,copy) TestBlockWithData block2;
@end
@implementation ZYTest
- (instancetype)initWithNameArr:(NSArray *)n{
self = [super init];
if (self) {
self.name = n;
_finishData = @"finished data";
}
return self;
}
- (void)testBlockWithBlk:(TestBlockWithData)block{
//将参数传给自己的属性,造成强引用
self.block2 = block;
[self requestFinished];
}
- (void)requestFinished{
//待操作完成后再调用block
if (_block2) {
_block2(_finishData);
}
}
调用方:
ZYTest *test = [[ZYTest alloc] initWithNameArr:@[@"11"]];
//block被test实例所持有,但是block里又引用了test实例本身,所以造成循环引用,内存泄漏
[test testBlockWithBlk:^(NSString *str) {
NSLog(@"test.name%@",test.name);
NSLog(@"test finishData:%@",str);
}];
ZYTest类之所以要将传入的block保存为自己的属性,是因为想在操作完成后再回调此block,但是回调后并未释放此block变量,又因为在调用方对block的实现中引用了实例test自身,所以造成循环引用,引起内存泄漏,这种情况较为隐蔽,不易发觉。
使用instrument观察leak:
解决这种内存泄漏的方法就是在使用完自身的block变量后将其置为nil,让其内存被自动回收就好了。
将上面的代码修改如下:
- (void)requestFinished{
if (_block2) {
_block2(_finishData);
NSLog(@"ZYTest requestFinished");
//在使用完blokc将其置为nil,回收内存
// _block2 = nil;
}
}