關(guān)于Block:
在我們使用OC進(jìn)行iOS開(kāi)發(fā)和Mac OS開(kāi)發(fā)中,Block語(yǔ)法是我們最常見(jiàn)的語(yǔ)法之一,而且蘋(píng)果官方也推薦我們使用Block代碼塊的語(yǔ)法,如果仔細(xì)觀察,大家可以看到,在近期蘋(píng)果新開(kāi)放的系統(tǒng)框架的API中,蘋(píng)果越來(lái)越多的使用了Block這種語(yǔ)法方式,而不是@selector方法選擇器.
蘋(píng)果如此推薦Block的使用,以及這種語(yǔ)法深受廣大OC開(kāi)發(fā)者的喜愛(ài)的原因是Block不可替代的優(yōu)點(diǎn):Block的寫(xiě)法非常簡(jiǎn)潔高效,而且可以減少代碼的分散,提高內(nèi)聚性等等,雖然代理等回調(diào)方式也有它的優(yōu)點(diǎn),但是廣大OC開(kāi)發(fā)者確大都對(duì)Block情有獨(dú)鐘.
接下來(lái),我們一起來(lái)看看Block的用法,原理,以及注意點(diǎn)吧.
初識(shí)Block:
Block 是在OSX 10.4 以及iOS4 之后引入的新的語(yǔ)法形式,從技術(shù)上來(lái)講,Block應(yīng)該屬于C語(yǔ)言的特性,所以只要編譯器支持,就可以在C,C++,OC,OC++中使用.
通俗點(diǎn)來(lái)說(shuō)Block的內(nèi)容其實(shí)就是一個(gè)代碼塊,表示一段代碼.但是這個(gè)代碼塊可以有返回值,可以有參數(shù)
Block也可以看成是一個(gè)OC對(duì)象,所以它也能賦值給變量.
沒(méi)有參數(shù),沒(méi)有返回值的Block
說(shuō)一百句話(huà)也不如上一句代碼,我們先來(lái)寫(xiě)個(gè)最簡(jiǎn)單的Block吧:
^{
NSLog(@"I am a Block");
};
很簡(jiǎn)單的語(yǔ)法形式對(duì)吧,一對(duì){}前面再加上一個(gè)^,就是最簡(jiǎn)單的Block形式,這個(gè)Block只有一句代碼,沒(méi)有返回值,也沒(méi)有參數(shù).
有參數(shù),沒(méi)有返回值的Block
^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
};
這個(gè)Block帶了兩個(gè)參數(shù)分別是count和str,參數(shù)就寫(xiě)在^后面的()中就可以了
有參數(shù),有返回值的Block
^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
這個(gè)Block不僅有兩個(gè)參數(shù),還有返回值,返回值的類(lèi)型是一個(gè)BOOL值
這些就是幾種Block的類(lèi)型啦,是不是也沒(méi)有那么復(fù)雜.
把Block賦值給變量
上面我們創(chuàng)建了三種類(lèi)型的Block,但是,我們卻無(wú)法方便的拿來(lái)使用.這個(gè)時(shí)候,我們需要把這個(gè)創(chuàng)建出來(lái)的Block賦值給一個(gè)變量:
BOOL (^aBlock)(NSInteger count,NSString *str)=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
我們把上面那個(gè)既有返回值又有參數(shù)的Block賦值給了一個(gè)叫aBlock的變量,這個(gè)變量名是寫(xiě)在中間的看起來(lái)有點(diǎn)奇怪,但是熟悉了Block的語(yǔ)法就會(huì)習(xí)慣了,而BOOL (^aBlock)(NSInteger count,NSString str)表示Block的類(lèi)型.BOOL表示返回值,(NSInteger count,NSString str)表示這個(gè)Block的兩個(gè)參數(shù).
通過(guò)變量名調(diào)用Block
我們?nèi)绻枰{(diào)用這個(gè)定義的Block,則可以通過(guò)變量名如這樣:
aBlock(10,@"123");
這樣我們就調(diào)用了這個(gè)Block,并為這個(gè)Block傳入了兩個(gè)參數(shù).
內(nèi)聯(lián)Block
我們使用Block經(jīng)常是把Block當(dāng)做一個(gè)參數(shù)傳出一個(gè)方法中,這種用法叫做內(nèi)聯(lián)Block.
我們直觀的從代碼上了解這種用法就好了:
-(void)aMethodWithInlineBlock:(BOOL(^)(NSString *,NSInteger ))block
{
if (block) {
block(@"123",10);
}
}
上面為一個(gè)帶Block參數(shù)的方法實(shí)現(xiàn),下面是調(diào)用這個(gè)方法的代碼:
[self aMethodWithInlineBlock:^BOOL(NSString *str,NSInteger count) {
NSLog(@"%@----%ld",str,count);
return YES;
}];
Block最大的特性:捕獲變量:
在Block的諸多特性中,最顯著的最強(qiáng)大的特性是可以在Block的聲明范圍中,捕獲需要的變量.實(shí)際上,我們可以把Block看做是一個(gè)OC對(duì)象,所以它也會(huì)用引用計(jì)數(shù)來(lái)管理,如果我們?cè)贐lock中使用了某個(gè)外部變量,而該變量是對(duì)象類(lèi)型的話(huà),Block也會(huì)對(duì)這個(gè)變量有一個(gè)強(qiáng)引用,當(dāng)Block本身的引用計(jì)數(shù)變?yōu)?的時(shí)候,系統(tǒng)會(huì)釋放Block,Block也會(huì)對(duì)所捕獲的對(duì)象做一次release操作.
Block內(nèi)存結(jié)構(gòu):
類(lèi)型 | 變量名 |
---|---|
void * | isa |
int | flag |
int | reserved |
void(*)(void) | invoke |
struct | descriptor |
捕獲到的變量 | 捕獲到的變量 |
這其中最重要的是invoke這個(gè)函數(shù)指針,它指向的是Block的實(shí)現(xiàn)代碼.
descriptor是一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中有Block的大小,還有copy,dispose這兩個(gè)函數(shù)指針,分別是在拷貝和丟棄Block的時(shí)候調(diào)用的.
Block會(huì)對(duì)它捕獲到的變量指針做一個(gè)拷貝.
在棧中的Block
我們定義Block時(shí),Block所占據(jù)的其實(shí)是棧內(nèi)存空間,在棧內(nèi)存空間中,那么我們就無(wú)法控制Block的生命周期,ARC也沒(méi)有辦法對(duì)這種Block進(jìn)行引用計(jì)數(shù)的內(nèi)存管理,這種Block通常是不安全的,例如:
void (^aBlock)();
if (self.condition) {
aBlock=^{
NSLog(@"123");
};
}else
{
aBlock=^{
NSLog(@"456");
};
}
aBlock();
這段代碼其實(shí)是不安全的,因?yàn)槲覀兌x出來(lái)的Block在棧內(nèi)存中,而我們不能保證在出了Block的定義區(qū)域后,這個(gè)Block還有效,那么在這種情況下,賦值給aBlock變量是不安全的,然而,我們只要對(duì)Block進(jìn)行一次copy操作,就能在堆內(nèi)存中拷貝一份Block,這樣我們就能對(duì)Block的內(nèi)存進(jìn)行控制了,ARC也能對(duì)copy出來(lái)的Block進(jìn)行內(nèi)存管理了.
上面的代碼如下改就變得安全了:
void (^aBlock)();
if (self.condition) {
aBlock=[^{
NSLog(@"123");
} copy];
}else
{
aBlock=[^{
NSLog(@"456");
}copy ];
}
aBlock();
為Block變量創(chuàng)建別名
我們上面說(shuō)到Block變量名是寫(xiě)在類(lèi)型中間的,看起來(lái)似乎有點(diǎn)奇怪,而且也不是非常有利于易讀性,所以,我們一般會(huì)為Block類(lèi)型起一個(gè)別名.
之前我們對(duì)一個(gè)Block變量賦值為這種寫(xiě)法:
BOOL (^aBlock)(NSInteger ,NSString *)=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
aBlock(10,@"123");
我們現(xiàn)在為這種類(lèi)型的Block起個(gè)別名
typedef BOOL (^ABlock)(NSInteger ,NSString *);
ABlock為這種Block的類(lèi)型名,我們之后再賦值給這種類(lèi)型的Block就可以如下寫(xiě)了:
ABlock aBlock=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
aBlock(10,@"123");
使用Block最大的注意點(diǎn):避免循環(huán)引用
我們剛剛有說(shuō)過(guò),在Block中,Block會(huì)自動(dòng)捕獲要使用到的變量,并持有該變量.這時(shí)往往容易出現(xiàn)循環(huán)引用的問(wèn)題,當(dāng)前類(lèi)實(shí)例持有該Block,該Block又在其中使用self,持有了該實(shí)例,這時(shí)循環(huán)引用問(wèn)題就出現(xiàn)了.
例如:我們?cè)诋?dāng)前類(lèi)聲明一個(gè)Block屬性:
@property (nonatomic,copy)ABlock ablock;
我們給這個(gè)Block賦值:
self.ablock=^(NSInteger count,NSString *str ){
self.view.backgroundColor=[UIColor greenColor];
return YES;
};
這樣,由于我們?cè)贐lock中使用了self,那么這個(gè)Block就會(huì)把當(dāng)前實(shí)例捕獲,并持有,而當(dāng)前實(shí)例又擁有該Block,這就導(dǎo)致了循環(huán)引用的狀況.
如下修改:
__weak ViewController * weakSelf=self;
self.ablock=^(NSInteger count,NSString *str ){
weakSelf.view.backgroundColor=[UIColor greenColor];
return YES;
};
我們?cè)谠揃lock中使用了self的若引用,從而打破了這個(gè)引用環(huán),解決了循環(huán)引用的問(wèn)題.
特別需要注意的一種情況是對(duì)在Block中使用成員變量,引起的循環(huán)引用問(wèn)題容易讓開(kāi)發(fā)者們所忽視,因?yàn)?在Block中雖然并沒(méi)有使用self,而是使用的成員變量,但是想要捕獲成員變量又一定要捕獲self,這依然會(huì)導(dǎo)致循環(huán)引用.例如:
//定義了一個(gè)成員變量
{
NSString *_chengyuanbianliang;
}
//在Block中使用成員變量,導(dǎo)致循環(huán)引用
self.ablock=^(NSInteger count,NSString *str ){
NSLog(@"%@",_chengyuanbianliang);
return YES;
};
我們可以如下解決這個(gè)問(wèn)題:
__weak ViewController * weakSelf=self;
self.ablock=^(NSInteger count,NSString *str ){
__strong ViewController *strongSelf=weakSelf;
NSLog(@"%@",strongSelf->_chengyuanbianliang );
return YES;
};