
一、何為Block?
普遍的解釋是:帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
其本質(zhì)是個(gè)結(jié)構(gòu)體。
- 匿名函數(shù) : 不帶有名字的函數(shù)
- 自動(dòng)變量 : 局部變量(可以傳遞值的變量),表現(xiàn)為 “截取自動(dòng)變量值”。Block 表達(dá)式截獲所使用的自動(dòng)變量的值,即保存該自動(dòng)變量的瞬間值。修飾為 __block 的變量,在捕獲時(shí),獲取的不再是瞬間值。
int b = 0;
void (^myBlock)() = ^{
NSLog(@"b=%d",b);
};
b = 1;
myBlock();
// 結(jié)果:b=0
雖然我們在調(diào)用myBlock之前改變了b的值,但是輸出的還是Block編譯時(shí)候b的值,所以截獲瞬間自動(dòng)變量就是:在Block中會(huì)保存變量的值,而不會(huì)隨變量的值的改變而改變。
Block聲明
返回值類型 block名稱 參數(shù)列表
void(^ BlockName)(int a)
Block定義
^ 返回值類型 參數(shù)列表 表達(dá)式
^ void(int count){return count + 1}
注:返回值類型,參數(shù)列表都可以被省略
常用情況是使用typedef定義Block類型,如下:
typedef void (^ TestBlock)(NSString *sendValue);
@property (nonatomic, copy) TestBlock testBlock;
if (self.testBlock) {
self.testBlock (@"TestValue");
}
__weak typeof(self) weakSelf = self;
testObject.testBlock = ^(NSString *sendValue){
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.testValue = sendValue;
};
Block作為參數(shù)使用:
// 1.定義一個(gè)形參為Block的OC函數(shù)
- (void)useBlock:(int(^)(int, int))aBlock
{
NSLog(@"result = %d", aBlock(300,200));
}
// 2.聲明并賦值定義一個(gè)Block變量
int(^addBlock)(int, int) = ^(int x, int y){
return x+y;
};
// 3.以Block作為函數(shù)參數(shù),把Block像對象一樣傳遞
[self useBlock:addBlock];
// 將第2點(diǎn)和第3點(diǎn)合并一起,以內(nèi)聯(lián)定義的Block作為函數(shù)參數(shù)
[self useBlock:^(int x, int y){
return x+y;
}];
在 Objective-C 語言中,一共有 3 種類型的 block:
1. _NSConcreteGlobalBlock 全局的靜態(tài) block,不會(huì)訪問任何外部變量。
2. _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷毀。
3. _NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷毀。
Block屬性的聲明,需要用copy修飾符
Block 的生命周期是和棧是綁定在一起的,隨時(shí)都有可能被釋放,為了不被提前釋放掉,需要 copy 之后讓 block 在堆上?;蛘哒f是為了讓Block在初始化作用域外可以進(jìn)行正常訪問外部變量。
修改 block 之外的變量
對于block外的變量引用,block默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的,如下圖:

通過block進(jìn)行閉包的外部變量是const只讀的。也就是說不能在block中直接修改這些變量,否則,編譯器會(huì)報(bào)錯(cuò)。
** 注:Block中可以直接修改靜態(tài)變量、全局變量**
當(dāng)我們需要在block中處理變量時(shí),可以用
__block關(guān)鍵字來聲明變量,這樣就可以在block中修改變量了。對于用__block修飾的外部變量引用,block是復(fù)制其引用地址來實(shí)現(xiàn)訪問的,如下圖:

block引起的內(nèi)存泄漏
在Block的內(nèi)存存儲(chǔ)在堆中時(shí),如果在Block中引用了外面的對象,會(huì)對所引用的對象進(jìn)行強(qiáng)引用,但是在Block被釋放時(shí)會(huì)自動(dòng)去掉對該對象的強(qiáng)引用,所以不會(huì)造成內(nèi)存泄漏。
Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock();
// Person對象在這里可以正常被釋放
如果對象內(nèi)部有一個(gè)Block屬性,而在Block內(nèi)部又訪問了該對象,那么會(huì)造成循環(huán)引用
@interface Person : NSObject
@property (nonatomic, copy) void(^myBlock)();
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
}
@end
Person *p = [[Person alloc] init];
// 情況一
p.myBlock = ^{
NSLog(@"------%@", p);
};
// 情況二
- (void)resetBlock
{
self.myBlock = ^{
NSLog(@"------%@", self);
};
}
p.myBlock();
// 情況一:因?yàn)閙yBlock作為Person的屬性,采用copy修飾符修飾(這樣才能保證Block在堆里面,以免Block在棧中被系統(tǒng)釋放),所以Block會(huì)對Person對象進(jìn)行一次強(qiáng)引用,導(dǎo)致循環(huán)引用無法釋放
// 情況二:Person對象在這里無法正常釋放,在resetBlock方法實(shí)現(xiàn)中,Block內(nèi)部對self進(jìn)行了一次強(qiáng)引用,導(dǎo)致循環(huán)引用無法釋放
解決方案:解決循環(huán)引用的辦法是使用一個(gè)弱引用的指針指向該對象即用__weak修飾,然后在Block內(nèi)部使用該弱引用指針來進(jìn)行操作,這樣避免了Block對對象進(jìn)行強(qiáng)引用
// 情況一
__weak typeof(p) weakP = p;
// 情況二
__weak typeof(self) weakSelf = self;
But有可能會(huì)出現(xiàn)這種情況:Block是通過弱引用指向了一個(gè)對象,那么有可能在調(diào)用Block之前這個(gè)對象便已經(jīng)被釋放了,所以我們需要在Block內(nèi)部再定義一個(gè)強(qiáng)指針來指向該對象。
__weak typeof(self) weakSelf = self;
testObject.testBlock = ^(NSString *sendValue){
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.testValue = sendValue;
};
在Block內(nèi)部定義的變量,會(huì)在作用域結(jié)束時(shí)自動(dòng)釋放,Block對其并沒有強(qiáng)引用關(guān)系,且在ARC中只需要避免循環(huán)引用即可,如果只是Block單方面地對外部變量進(jìn)行強(qiáng)引用,并不會(huì)造成內(nèi)存泄漏
親,喜歡的話點(diǎn)個(gè)贊唄!