Block基本語法
typedef聲明,作Method參數(shù)使用
typedef void(^typedefBlock)(int age,NSString *str);
- (void)blockTypedefMethodWithBlock:(typedefBlock)callBack{
callBack(18,@"is my age");
}
屬性聲明,使用copy
修飾符
@property (nonatomic, copy) typedefBlock myBlock;
基本語法
/**
block基本語法
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
*/
- (void)blockBasicGrammar{
//有參無返回值
void(^block1)(int index,NSString *name) = ^void(int index,NSString *name){
NSLog(@"index = %d name = %@",index,name);
};
block1(3,@"kelly");
}
聲明Block屬性使用copy修飾詞
Block三種類型
- 全局塊(_NSConcreteGlobalBlock)
- 棧塊(_NSConcreteStackBlock)
- 堆塊(_NSConcreteMallocBlock)
存儲(chǔ)區(qū)域
1. 全局塊存儲(chǔ)在靜態(tài)區(qū)(也叫全局區(qū)),相當(dāng)于OC中的單例;
2. 棧塊存儲(chǔ)在棧區(qū),超出作用域馬上被銷毀;
3. 堆塊存儲(chǔ)在堆區(qū),是一個(gè)帶引用計(jì)數(shù)的對(duì)象,需自行管理內(nèi)存。
判斷block存儲(chǔ)位置
- block不訪問外界變量(包括棧中和堆中的變量):block既不在棧中也不在堆中,此時(shí)為全局塊,ARC和MRC都如此;
- block訪問外界變量:
?MRC:默認(rèn)存儲(chǔ)在棧區(qū);
?ARC:默認(rèn)存儲(chǔ)在堆中,實(shí)際上是先放在棧區(qū),在ARC情況下自動(dòng)拷貝到堆區(qū),自動(dòng)釋放。
block使用copy修飾符的作用:將block從棧區(qū)拷貝到堆區(qū)。
?原因:保存block狀態(tài),延長生命周期。因?yàn)閎lock存儲(chǔ)在棧,其所屬變量作用域結(jié)束,該block就被釋放,__block
變量也同時(shí)被釋放掉,所以需要copy到堆中。
注意:不同類型block使用copy
效果也不一樣,如下圖:
__block的使用(改變block的存儲(chǔ)區(qū))
例子1,不使用__block
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
輸出結(jié)果:Integer is : 42
例子2,使用__block
- (void)testMethod {
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is : %i", anInteger);
};
anInteger = 50;
testBlock();
}
輸出結(jié)果:Integer is : 50
分析
?例子1:block把a(bǔ)ninteger變量復(fù)制為自己私有的const變量,也就是說block會(huì)捕獲棧上的變量或指針,將其復(fù)制為自己私有的const變量。在進(jìn)行aninterger=50的操作時(shí),block已經(jīng)將其復(fù)制為自己的私有變量,所以修改aninterger的值不會(huì)對(duì)block里面的值造成影響。
?例子2:aninteger是一個(gè)局部變量,存儲(chǔ)在棧區(qū)。給aninteger加入__block
修飾符的作用是只要觀察到該變量被block持有,就將該變量在棧中的內(nèi)存地址存在到堆中,此時(shí)不管block外部還是內(nèi)部aninteger的內(nèi)存地址都是一樣的,進(jìn)而可以修復(fù)aninteger變量的值。
??圖例
block循環(huán)引用
循環(huán)引用例子
@property (nonatomic, copy) void (^block)(void);
- (void)configureBlock {
self.block = ^{
[self doSomething];
};
}
?在上面代碼中聲明了一個(gè)屬性block,所以self對(duì)block有一個(gè)強(qiáng)引用。而block內(nèi)部又對(duì)self進(jìn)行一次強(qiáng)引用,這樣就形成了一個(gè)封閉的環(huán),也就是強(qiáng)引用循環(huán)。引用關(guān)系如圖:
?在這種情況下,由于相互引用,內(nèi)存不能進(jìn)行釋放,造成內(nèi)存泄漏。解決方案:
__weak
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething];
};
}
?使用__weak
之后,block對(duì)self就由強(qiáng)引用變?yōu)槿跻茫@樣在屬性所指的對(duì)象被釋放時(shí),屬性值也會(huì)被釋放,打破了block捕獲作用域帶來的循環(huán)引用。
?需要注意的是:使用系統(tǒng)的block API不需要考慮循環(huán)引用問題,因?yàn)橹挥衎lock對(duì)self進(jìn)行了一次強(qiáng)引用,屬于單向強(qiáng)引用,沒有形成循環(huán)引用。
weak與strong區(qū)別
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption(搶占) weakSelf turned nil
[weakSelf doAnotherThing];
};
}
上面這段代碼看上去很正常,但是在并發(fā)執(zhí)行的時(shí)候,block的執(zhí)行時(shí)可以搶占的,而且對(duì)weakSelf指針的調(diào)用時(shí)序不同可以導(dǎo)致不同的結(jié)果,比如在一個(gè)特定時(shí)序下weakSelf可能變成nil,這個(gè)時(shí)候執(zhí)行doAnotherThing
就會(huì)崩潰。為避免這樣的問題,使用__strong
進(jìn)行規(guī)避,代碼修改:
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething]; // strongSelf != nil
// 在搶占的時(shí)候,strongSelf還是非nil的。
[strongSelf doAnotherThing];
};
}
總結(jié)
- block不作為
property
的時(shí)候,可以在block里面直接使用self,比如UIView的animation動(dòng)畫block; - block被聲明
property
的時(shí)候,需要使用__weak
弱引用,并解決循環(huán)引用的問題; - 當(dāng)和并發(fā)執(zhí)行相關(guān)、涉及異步的服務(wù)的時(shí)候,block可以在之后被執(zhí)行,并且不會(huì)發(fā)生關(guān)于self是否存在的問題。