1 什么是Block
我們先來(lái)看一段代碼:
[UIView animateWithDuration:0.2
animations:^{view.alpha = 0.0;}
completion:^(BOOL finished){ [view removeFromSuperview]; }];
上面的代碼來(lái)自UIView的animateWithDuration:animations:completion:方法,用于執(zhí)行在視圖的動(dòng)畫(huà)過(guò)程中淡化視圖直到完全透明垒酬,然后從視圖層次結(jié)構(gòu)中刪除視圖的操作赶么。該方法一共有三個(gè)參數(shù)登澜,其中animations
和completion
都是以脫字符^
開(kāi)頭的block對(duì)象抢蚀,這是框架方法中使用block的典型示例之一地沮。
塊(Block)是蘋(píng)果在iOS4開(kāi)始引入的對(duì)C語(yǔ)言的一種擴(kuò)展风响,它允許我們創(chuàng)建不同的代碼段嘉汰,與函數(shù)類似,不同的是状勤,塊定義在函數(shù)或方法內(nèi)部鞋怀,并能夠訪問(wèn)在函數(shù)或方法范圍內(nèi)而塊之外的任何變量。一般來(lái)說(shuō)持搜,這些變量能夠訪問(wèn)但是它們的值并不能被改變密似。只有通過(guò)在變量前添加一個(gè)特殊的塊修改器__block
(在block前面添加兩個(gè)下劃線的字符組成)才能修改該變量的值,后面我們會(huì)有詳細(xì)介紹朵诫。同時(shí)辛友,塊也是Objective-C對(duì)象,因此它們可以存儲(chǔ)到NSArray或NSDictionary數(shù)據(jù)結(jié)構(gòu)中剪返,并且作為返回值從方法返回废累,甚至被分配給變量。
在這里我們主要介紹block的聲明脱盲、創(chuàng)建邑滨、使用,以及在block內(nèi)部訪問(wèn)變量等相關(guān)知識(shí)钱反。
2 聲明并創(chuàng)建Block
2.1 Block的聲明
返回值類型 (^block名稱) (參數(shù)列表);
下面是幾種有效的block聲明:
void (^blockReturningVoidWithVoidArgument)(void);
void (^blockReturningVoidWithIntAndCharArguments)(int, char);
int (^blockReturningIntWithVoidArgument)(void);
NSString *(^blockReturningNSStringWithNSStringArgument)(NSString *);
NSString *(^blockReturningNSStringWithNSStringArgument)(NSString *string);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
有幾點(diǎn)值得注意的地方:
- block的聲明和函數(shù)類似掖看,如果不返回任何內(nèi)容,則返回類型為
void
面哥,且不可省略哎壳。 - block名稱前面的脫字符
^
不能省略,塊名稱的命名規(guī)則與其它變量名或方法名的命名規(guī)則一致尚卫,并且脫字符和塊名稱都在圓括號(hào)內(nèi)归榕。 - 參數(shù)列表必須在括號(hào)內(nèi),如果有多個(gè)參數(shù)吱涉,參數(shù)間用逗號(hào)隔開(kāi)刹泄。如果不傳遞參數(shù)外里,設(shè)置為
void
。如果有參數(shù)特石,必須標(biāo)明參數(shù)類型盅蝗,參數(shù)名稱僅助于開(kāi)發(fā)人員記住其功能,可以忽略姆蘸。 - 最后墩莫,不要忘記寫(xiě)分號(hào)
;
。
2.2 Block的創(chuàng)建與調(diào)用
定義一個(gè)Block:
^(參數(shù)列表){
... 塊主體 ...
};
與聲明不同的是乞旦,在block定義中贼穆,如果有參數(shù),參數(shù)名稱不能省略兰粉;如果沒(méi)有參數(shù),則可以省略(void)
參數(shù)列表顶瞳。
調(diào)用一個(gè)Block:
// 有參數(shù):
block名稱(參數(shù));
// 無(wú)參數(shù):
block名稱();
代碼示例:
- 無(wú)返回內(nèi)容玖姑,無(wú)參數(shù):
// 1 聲明一個(gè)block
void (^block)(void);
// 2 定義block(為block賦值)
block = ^{
NSLog(@"該block沒(méi)有參數(shù),不返回任何內(nèi)容慨菱。");
};
// 3 調(diào)用block
block();
也可以將block的聲明和定義合并在一起焰络,如下:
// 1 創(chuàng)建block
void (^block)(void) = ^{
NSLog(@"該Block沒(méi)有參數(shù),不返回任何內(nèi)容符喝。");
};
// 2 調(diào)用block
block();
- 無(wú)返回內(nèi)容闪彼,有參數(shù):
void (^block)(int);
block = ^(int value){
NSLog(@"The value is %i", value);
};
block(3);
輸出結(jié)果:
The value is 3
- 有返回內(nèi)容,無(wú)參數(shù):
int (^block)(void);
block = ^{
int value = 3;
return value;
};
NSLog(@"The value is %i", block());
輸出結(jié)果:
The value is 3
- 有返回內(nèi)容协饲,有參數(shù):
int (^block)(int, int);
block = ^(int a, int b){
int value = a + b;
return value;
};
NSLog(@"The value is %i", block(3, 5));
輸出結(jié)果:
The value is 8
2.3 使用typedef定義Block
如果需要重復(fù)聲明多個(gè)相同類型(即返回值類型相同畏腕,參數(shù)列表相同)的block,我們可以使用typedef
來(lái)定義block類型茉稠,以此來(lái)避免編寫(xiě)重復(fù)的代碼描馅,示例如下:
// 1 聲明一種返回值為int,并且有兩個(gè)參數(shù)均為int的block類型
typedef int (^Block)(int, int);
// 2 使用該block類型聲明并定義兩個(gè)變量block1和block2
Block block1 = ^(int a, int b){
return a + b;
};
Block block2 = ^(int a, int b){
return a * b;
};
// 3 調(diào)用
NSLog(@"The value of block1 is %i", block1(3, 5));
NSLog(@"The value of block2 is %i", block2(3, 5));
輸出結(jié)果:
The value of block1 is 8
The value of block2 is 15
2.4 將Block聲明為全局變量
Block與其它變量一樣而线,也可以被聲明為全局變量铭污,示例如下:
#import "ViewController.h"
// 聲明并定義一個(gè)block為全局變量
NSString *(^globalBlock)(void) = ^{
return @"This is a global block";
};
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 調(diào)用
NSLog(@"%@", globalBlock());
}
3 Block與變量
Block與函數(shù)類似,能夠訪問(wèn)在函數(shù)或方法范圍內(nèi)而在塊之外定義的任何變量膀篮。
3.1 Block內(nèi)訪問(wèn)全局變量嘹狞,包括靜態(tài)變量
#import "ViewController.h"
// 聲明一個(gè)全局變量
int gGlobalVar = 5;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void (^block)(void) = ^{
// 1 訪問(wèn)全局變量
NSLog(@"gGlobalVar = %i", gGlobalVar);
// 2 直接修改全局變量的值
gGlobalVar = 8;
NSLog(@"gGlobalVar = %i", gGlobalVar);
};
// 調(diào)用
block();
// 3 調(diào)用前修改全局變量的值
gGlobalVar = 6;
block();
}
輸出結(jié)果:
gGlobalVar = 5
gGlobalVar = 8
gGlobalVar = 6
gGlobalVar = 8
從上面代碼及輸出結(jié)果,我們不難發(fā)現(xiàn):
- 在block內(nèi)可以訪問(wèn)全局變量的值誓竿。
- 在block中可以直接修改全局變量的值磅网。
- 在block定義后、調(diào)用前修改全局變量的值烤黍,會(huì)影響輸出結(jié)果知市。當(dāng)調(diào)用block時(shí)傻盟,訪問(wèn)的全局變量的值是修改后的新值。這是因?yàn)槿肿兞浚ɑ蜢o態(tài)變量)在內(nèi)存中的地址是固定的嫂丙,block在讀取該變量值的時(shí)候是直接從其所在的內(nèi)存中讀取娘赴,因此得到的是最新值,而不是在定義時(shí)copy的常量跟啤。
block也可以訪問(wèn)靜態(tài)變量诽表、修改靜態(tài)變量的值等,與全局變量類似隅肥,這里不再贅述竿奏。
<a id = "3.2">
3.2 Block訪問(wèn)局部變量
- (void)viewDidLoad {
[super viewDidLoad];
// 定義一個(gè)局部變量
int var = 5;
void (^block)(void) = ^{
// 1 訪問(wèn)局部變量
NSLog(@"var = %i", var);
// 2 修改局部變量的值
var = 8; //編譯時(shí)會(huì)報(bào)錯(cuò)
NSLog(@"var = %i", var);
};
// 調(diào)用
block();
// 3 調(diào)用前修改局部變量的值
var = 6;
block();
}
上面var = 8;
所在的代碼行會(huì)出現(xiàn)錯(cuò)誤提示:Variable is not assignable(missing __block type specifier),這是因?yàn)樵赽lock內(nèi)部不能直接修改局部變量的值腥放,如果要修改泛啸,必須使用__block
修飾符重新聲明局部變量。這里我們先注釋掉2
中的代碼秃症,看一下運(yùn)行結(jié)果:
var = 5
var = 5
由此可見(jiàn):
- 在block內(nèi)可以訪問(wèn)局部變量的值候址。
- 在block內(nèi)不能直接修改局部變量的值,如果要修改必須使用
__block
修飾符聲明局部變量种柑。 - 在block定義后岗仑、調(diào)用前修改局部變量的值,不影響輸出結(jié)果聚请,當(dāng)調(diào)用block時(shí)荠雕,訪問(wèn)的局部變量的值是修改前的舊值。這是因?yàn)閎lock在定義時(shí)會(huì)copy變量的值作為常量使用驶赏,因此局部變量在block中是只讀的炸卑,即使變量的值在block外改變,也不影響其在block中的值母市。
</a>
3.3 Block訪問(wèn)__block
修飾的局部變量
在這里我們使用__block
存儲(chǔ)類型修飾符(由下劃線下劃線block組成)重新定義局部變量矾兜,以解決3.2中遇到的錯(cuò)誤提示:
- (void)viewDidLoad {
[super viewDidLoad];
// 使用__block重新定義局部變量
__block int var = 5;
void (^block)(void) = ^{
// 1 訪問(wèn)局部變量
NSLog(@"var = %i", var);
// 2 修改局部變量的值
var = 8;
NSLog(@"var = %i", var);
};
// 調(diào)用
block();
// 3 調(diào)用前修改局部變量的值
var = 6;
block();
}
這次沒(méi)有任何錯(cuò)誤提示,并且輸出結(jié)果如下:
var = 5
var = 8
var = 6
var = 8
由此我們發(fā)現(xiàn):
- 被
__block
修飾的局部變量患久,在block內(nèi)可以正常訪問(wèn)該變量的值椅寺。 - 被
__block
修飾的局部變量,在block內(nèi)可以修改該變量的值蒋失。 - 被
__block
修飾的局部變量返帕,在block定義后、調(diào)用前修改該變量的值會(huì)影響輸出結(jié)果篙挽,調(diào)用block時(shí)荆萤,訪問(wèn)的該變量的值是修改后的新值。
4 Block的使用
我們已經(jīng)知道block被廣泛地用在很多框架方法中,除此之外链韭,它還常用于GCD偏竟、動(dòng)畫(huà)、排序及各類回調(diào)中敞峭,在這里我們主要介紹block作為參數(shù)和屬性的用法踊谋。
4.1 Block作為方法或函數(shù)的參數(shù)
我們將block作為參數(shù)傳遞給方法或函數(shù)的過(guò)程與其它參數(shù)類似,需要注意的是旋讹,一般情況下殖蚕,我們只對(duì)方法使用一個(gè)block參數(shù),如果該方法還需要其它非block的參數(shù)沉迹,則該block參數(shù)應(yīng)該是方法的最后一個(gè)參數(shù)硼控。
// 定義一個(gè)含有block參數(shù)的方法:
- (void)addNumber:(int)number1 withNumber:(int)number2 withBlockArgument:(void (^)(int))block
{
int result = number1 + number2;
block(result);
}
// 在viewDidLoad中調(diào)用該方法:
- (void)viewDidLoad {
[super viewDidLoad];
[self addNumber:3 withNumber:5 withBlockArgument:^(int result) {
NSLog(@"The result is %i", result);
}];
}
輸出結(jié)果:
The result is 8
4.2 Block被用作屬性
Block作為屬性時(shí)用法與其它屬性類似萧芙,不同的是,使用block作為屬性時(shí)钥顽,在MRC環(huán)境下只能用copy
修飾再膳。這是因?yàn)樵谑褂胋lock訪問(wèn)外部變量時(shí)渺贤,block類型是堆block拄轻,當(dāng)堆中的block引用計(jì)數(shù)為0被銷毀時(shí)婚度,如果想繼續(xù)在外部訪問(wèn)和調(diào)用block,就需要將block從棧中復(fù)制一份移動(dòng)到堆中进宝,因此需要用copy
修飾。如果是在ARC環(huán)境下這些會(huì)自動(dòng)發(fā)生枷恕,我們不需要擔(dān)心党晋,可以用strong
修飾。
下面是一段簡(jiǎn)單的代碼示例:
#import "ViewController.h"
@interface ViewController ()
// 聲明一個(gè)block屬性
@property (nonatomic, copy) void (^blockAsProperty)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 設(shè)置該block屬性
self.blockAsProperty = ^{
NSLog(@"Using block as a property.");
};
// 調(diào)用
self.blockAsProperty();
}
也可以使用typedef
來(lái)聲明block屬性:
typedef void (^blockAsProperty)(void);
@interface ViewController ()
@property (nonatomic, copy) blockAsProperty blockProperty;
@property (nonatomic, copy) blockAsProperty anotherBlockProperty;
@end
另外徐块,block作為屬性多被用于逆向傳值未玻,如下圖所示,我們將第二個(gè)視圖控制器中設(shè)置的顏色通過(guò)block屬性傳遞給第一個(gè)視圖控制器做背景顏色胡控。在這里我們不詳細(xì)介紹實(shí)現(xiàn)過(guò)程扳剿,如果需要可以下載BlockDemo查看。
4.3 Block在框架方法中的使用
文章的開(kāi)頭我們就展示了在框架方法中使用block的示例昼激,在這里只列舉一下block在系統(tǒng)框架中的幾種用法庇绽,詳細(xì)內(nèi)容可以參閱文檔中關(guān)于Blocks in the System Framework APIs的講解。
Block在系統(tǒng)框架方法中主要被用作以下幾種形式:
- 完成處理程序 (Completion handlers)
- 通知處理程序 (Notification handlers)
- 錯(cuò)誤處理程序 (Error handlers)
- 枚舉 (Enumeration)
- 視圖動(dòng)畫(huà)和轉(zhuǎn)場(chǎng) (View animation and transitions)
- 排序 (Sorting)
5 Block的內(nèi)存管理
Block在ARC和MRC下的內(nèi)存管理是不同的橙困,在這里我們只介紹block在ARC下的內(nèi)存管理瞧掺,在MRC下的內(nèi)存管理可以參考這里。
在ARC默認(rèn)情況下凡傅,block是存儲(chǔ)在堆中辟狈,ARC會(huì)自動(dòng)進(jìn)行內(nèi)存管理,我們只需要避免循環(huán)引用即可。下面我們就介紹下在什么情況會(huì)造成循環(huán)引用以及如何去避免哼转。
5.1 Block中的循環(huán)引用
Block會(huì)對(duì)包含self
在內(nèi)的所引用的對(duì)象進(jìn)行強(qiáng)引用明未,如果對(duì)象內(nèi)部有一個(gè)block屬性,而在block內(nèi)又訪問(wèn)了該對(duì)象壹蔓,這時(shí)就會(huì)造成循環(huán)引用趟妥。示例如下:
@interface ViewController ()
// 聲明block變量
@property (nonatomic, copy) void (^block)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
// 在block內(nèi)訪問(wèn)self
[self testMethod]; //編譯時(shí)會(huì)出現(xiàn)警告提示
NSLog(@"Block");
};
self.block();
}
- (void)testMethod
{
NSLog(@"Test method");
}
上面[self testMethod];
所在的代碼行會(huì)出現(xiàn)警告提示:Capturing 'self' strongly in this block is likely to lead to a retain cycle,這是因?yàn)樵赽lock內(nèi)部對(duì)self
進(jìn)行了一次強(qiáng)引用庶溶,會(huì)導(dǎo)致循環(huán)引用無(wú)法釋放煮纵。
5.2 避免循環(huán)引用
為了避免循環(huán)引用,最好的是對(duì)block內(nèi)部引用的self
對(duì)象進(jìn)行弱引用偏螺。一般使用一個(gè)弱指針來(lái)指向該對(duì)象行疏,然后在block內(nèi)使用該弱引用指針來(lái)進(jìn)行操作,這樣就避免了block對(duì)該對(duì)象的強(qiáng)引用套像。
對(duì)于上面的代碼酿联,我們使用一個(gè)弱指針對(duì)self
進(jìn)行弱引用,這樣警告就會(huì)消失夺巩,修改viewDidLoad
方法中的代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
__weak ViewController *weakSelf = self;
self.block = ^{
[weakSelf testMethod];
NSLog(@"Block");
};
self.block();
}
注意:這里的
__weak
是在weak前添加兩個(gè)下劃線字符組成的贞让,而且只能用于iOS5之后的版本。