本文內(nèi)容
- 什么是block?
- block的用途
- block的用法
- block在使用中遇到的問題
- 如何使用xcode檢測循環(huán)引用引發(fā)的內(nèi)存問題?
什么是block?
什么是閉包(closure)?
閉包是可以包含自由(未綁定到特定對象)變量的代碼塊。
什么是函數(shù)式編程?
函數(shù)可以隨時創(chuàng)建、作為參數(shù)傳遞、作為返回值返回。
在函數(shù)式編程中,把函數(shù)當(dāng)參數(shù)來回傳遞,而這個,說成術(shù)語,我們把他叫做高階函數(shù)。
Objective-C在沒有block之前,沒有類似的機(jī)制,有了block,Objective-C也就具備了函數(shù)式編程的能力,block是對象,有自己的ISA指針,可以隨時創(chuàng)建,作為參數(shù)傳遞,作為返回值返回
block是帶有局部變量的匿名函數(shù)(即沒有名稱的函數(shù)),就是OC中的閉包(closure),又名匿名函數(shù),塊函數(shù),塊
block的用途
block都是一些簡短代碼片段的封裝,適用作工作單元,通常用來做并發(fā)任務(wù)、遍歷、以及回調(diào)。
block的用法
- block 語法,用插入符號"^"定義一個block字面量,無參,無返回值的block 如下
^{
NSLog(@"This is A Block");
}();
控制臺輸出:
2015-11-03 12:04:26.266 Whisper[63849:545630] This is a block
- block的類型(可以用isa 指針指向的地址查看)
- NSConcreteGlobalBlock 全局的靜態(tài) block,不會訪問任何外部變量。
- NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時會被銷毀。
- NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計數(shù)為 0 時會被銷毀。
- 在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block
- block Pointer,主要用來簡化block的寫法,block Pointer定義如下:
回傳值 (^名字)(參數(shù)列表);
block Pointer 具體使用
int (^myBlock) (int a); // 聲明一個名字為myBlock的block 指針,該指針指向的Block有一個int輸入和一個int 輸出
myBlock = ^(int a){ return a*a;}; //將Block的實體指定給myBlock指針
int result = myBlock(4); //調(diào)用blcok實體
- 使用typedef給復(fù)雜變量block定義類型別名,定義規(guī)則:
在傳統(tǒng)的變量聲明表達(dá)式里用類型名替代變量名,然后把關(guān)鍵字typedef加在該語句的開頭,示例如下:
typedef int (^myBlock) (int a); //使用定義的新類型myBlock來聲明對象,等價于int (^myBlock) (int a);
一般借助block的類型別名,為特定的類添加block屬性變量,做傳值或者事件使用。聲明block屬性變量的時候,property中需設(shè)置成copy
- 用block來存取變量
一個Block的內(nèi)部是可以引用自身作用域外的變量的,包括static變量,extern變量或自由變量,對于自由變量,在Block中是只讀的。在引入block的同時,還引入了一種特殊的__block關(guān)鍵字,變量存儲修飾符,將變量的存儲范圍擴(kuò)展為該函數(shù)以及該函數(shù)內(nèi)定義的block的行為主體內(nèi)(蘋果官方文檔)。
存取靜態(tài)變量
static int outA = 8;
int (^myPtr)(int) = ^(int a){return outA + a;};
outA = 5;
int result = myPtr(3); //result的值是8,因為outA是static類型的變量
存取自由變量
{
__block int num = 5;
int (^myPtr)(int) = ^(int a){return num++;};
int (^myPtr2)(int) = ^(int a){return num++;};
int result = myPtr(0); //result的值為5,num的值為6
result = myPtr2(0); //result的值為6,num的值為7
NSLog(@"result=%d", result);
}
- block作為類的屬性
typedef int (^MyBlock) (int a);
@property(nonatomic,copy) int (^block)(int a); //使用c的方式, 不能使用OC函數(shù)形參的寫法.
@property (nonatomic,copy) MyBlock blockName;
- block作為函數(shù)參數(shù),蘋果官方文檔建議一個方法最好只有一個block參數(shù),并且block參數(shù)一般作為最后一個參數(shù)
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}
- (void)beginFetchWithCallbackBlock:(MyBlock)callbackBlock {
...
callbackBlock(3);
}
- 以block作為函數(shù)返回值
-(int (^)(int))blockBack{
return ^(int cout){ return cout;};
}
block在使用中遇到的問題
- 修改局部變量需要在局部變量前面加__block修飾符,將變量的存儲范圍擴(kuò)展為該函數(shù)以及該函數(shù)內(nèi)定義的block的行為主體內(nèi)。
- 在屬性定義一個block的時候需要使用copy,因為塊是在棧上分配的,一旦離開作用域, 就會釋放, 因此如果你要把塊用在別的地方, 必須要復(fù)制一份
- 在ARC下, 以下幾種情況, Block會自動被從棧復(fù)制到堆
- 被執(zhí)行copy方法
- 作為方法返回值
- 將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞的時候.
- 循環(huán)引用的問題
- A和B兩個對象,A持有B,B同時也持有A,A只有B釋放之后才有可能釋放,同樣B只有A釋放后才可能釋放,當(dāng)雙方都在等待對方釋放的時候, retain cycle就形成了,結(jié)果是,兩個對象都永遠(yuǎn)不會被釋放,最終內(nèi)存泄露。
- 循環(huán)引用(retain cycle)的解決
- 盡量保持子對象引用父對象的時候使用弱引用,也就是assign,比如
@property (nonatomic,assign) NSObject *parent;
- 及時地將造成retain cycle中的一個變量設(shè)置為nil,將環(huán)break掉
- block中的retain cycle
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a reference cycle
};
}
...
@end
-
block中retain cycle 的解決
- 方法一 將引用的一方變成weak,從而避免循環(huán)引用
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
或者
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
//如果想防止 weakSelf 被釋放,可以再次強(qiáng)引用
typeof(weakSelf) strongSelf = weakSelf;
[weakSelf doSomething]; // capture the weak reference cycle
}
}
- 方法二.使用完某對象沒有必要在保留該對象的時候,在block里面將對象釋放即可打破保留環(huán)
- (void)downloadData {
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
_fetchedData = data;
_networkFetcher = nil; //加上此行,此處是為了打破循環(huán)引用
}];
}
- 方法三. 在調(diào)用完block之后,將該block設(shè)置為nil(block為某類的屬性的時候,這么使用)
- (void)p_requestCompleted {
if(_completionHandler) {
_completionHandler(_downloadData);
}
self.completionHandler = nil;//加上此行,此處是為了打破循環(huán)引用
}
如何使用xcode檢測循環(huán)引用(引用自《iOS開發(fā)進(jìn)階》)?
- Xcode 的Instruments工具集可以很方便地檢測循環(huán)引用,但是檢測不出block產(chǎn)生的循環(huán)引用,示例如下
- (void)viewDidLoad {
[super viewDidLoad];
//firstArray 持有secondArray, secondArray 持有 firstArray,形成retain cycle
NSMutableArray *firstArray = [NSMutableArray array];
NSMutableArray *secondArray = [NSMutableArray array];
[firstArray addObject:secondArray];
[secondArray addObject:firstArray];
}
- 在Xcode 的菜單欄,選擇“Product”--->“Profile”,在調(diào)出的界面中選擇"Leaks"--->"choose",調(diào)出Instruments界面。
Instruments會用紅色的X表示一次內(nèi)存泄露的產(chǎn)生,Instruments中可以通過切換到“Leaks”,單擊“Cycles&Roots”,就可以看到以圖形方式顯示出來的循環(huán)引用,這樣,我們就可以很方便的看到產(chǎn)生循環(huán)引用的對象了。
-
具體使用步驟如下:
Instruments