參考書籍:《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;
}
}