前言
1、理解
Block其實就是一個代碼塊。本質(zhì)上來說,一個Block就是一段能夠在將來被執(zhí)行的代碼。然而Block又是一個普通的Objective-C對象,正因為它是對象,Block可以被作為參數(shù)傳遞,可以作為返回值從一個方法返回,可以用來給變量賦值。
2、特點
將代碼放在Block中,使代碼更簡潔緊湊,易于閱讀, 而且比函數(shù)使用更方便、更美觀。Block其實是對閉包的實現(xiàn)。
3、Block的優(yōu)勢
在Block之前,如果我們想要調(diào)用一段代碼,然后之后一段時間,讓它給我們返回,我們一般會使用delegate或者NSNotification。但是使用過 delegate 和 NSNotification 大家就應(yīng)該會感覺到——我們會不可避免的將代碼寫的到處都是,我們需要在某處開始一個任務(wù),在另外一個地方來處理這個返回結(jié)果。使用 Block 就可以在一定程度上避免這個問題。
下面進入主題
Block是什么
Block : 帶有自動變量(局部變量)的匿名函數(shù)
匿名函數(shù) :沒有函數(shù)名的函數(shù),一對{}包裹的內(nèi)容是匿名函數(shù)的作用域
自動變量:棧上聲明的一個變量不是靜態(tài)變量和全局變量,是不可以在這個棧內(nèi)聲明的匿名函數(shù)中使用的,但在Block中卻可以。
關(guān)于帶有自動變量的含義,這是因為Block有捕獲外部變量的功能。能夠保存外部變量的瞬間值,所以即便在block外修改變量的值,也不會對Block截獲的自動變量的值產(chǎn)生影響。
例 一道比較經(jīng)典的面試題:
int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n",val);
};
val = 2;
blk();
我們都知道這段代碼 輸出val的值為 10 而不是2,這是因為Block截獲變量并保存變量的瞬間值
Block 語法
- Block的聲明及定義
//返回值(^Blok名字)(參數(shù)列表) = ^返回值(參數(shù)列表) {實現(xiàn)};
//標(biāo)準(zhǔn)的定義和聲明
int(^blk)(int count) = ^int(int count) {
return count++;
};
blk(1);
//當(dāng)返回值為void 實現(xiàn)部分可以忽略void不寫 即 ^(int count){}
void(^blk1)(int count) = ^void(int count) {
count++;
};
blk1(1);
//當(dāng)參數(shù)為void 實現(xiàn)部分可以忽略參數(shù)不寫 即 ^int{}
int(^blk2)(void) = ^int(void) {
return 1;
};
blk2();
//當(dāng)參數(shù)和返回值都為void 實現(xiàn)部分可以簡寫為 ^{}
void(^blk3)(void) = ^void(void) {
NSLog(@"參數(shù)和返回值都為void");
};
blk3();
//匿名Block 只有實現(xiàn)部分 沒有函數(shù)名
// ^int(int count) {
// return count;
// };
- typedef簡化Block的聲明
//typedef 定義block 返回值(^Blok名字)(參數(shù)列表)
typedef void(^blk)(void); //無返回值 無參數(shù)
typedef void(^blk1)(NSString *name);//無返回值 有參數(shù)
typedef int(^blk2)(void);//有返回值 無參數(shù)
typedef int(^blk3)(int count);//有返回值 有參數(shù)
Block類型
根據(jù)Block在內(nèi)存中存儲的位置分為三種類型:
- NSGlobalBlock是位于全局區(qū)的block
- NSMallocBlock是位于堆區(qū)的block
- NSStackBlock是位于棧區(qū)的block
//全局block NSGlobalBlock
void(^blk)(void) = ^{
NSLog(@"blk");
};
blk();
NSLog(@" -- %@",blk);
void(^blk3)(int count) = ^(int count) {
NSLog(@"%d",count);
};
blk3(20);
NSLog(@" -- %@",blk3);
NSLog(@"-----------------------------------------");
//堆block NSMallockBlock
int a = 10;
void(^blk1)(void) = ^{
NSLog(@"a = %d",a);
};
blk1();
NSLog(@" -- %@",blk1);
__block int b = 30;
void(^blk4)(void) = ^{
b = 40;
NSLog(@"%d",b);
};
blk4();
NSLog(@" -- %@",blk4);
NSLog(@"-----------------------------------------");
//棧block NSStackBlock 當(dāng)不捕獲變量a時 該block為全局block
NSLog(@" -- %@", [^{NSLog(@"Stack Block:%d",a);} class]);
Block 用法 (傳遞數(shù)據(jù) 傳遞事件)
- 作為屬性
@interface ViewController ()
@property (copy, nonatomic) void(^blkCall)(NSInteger index);
@end
- 作為參數(shù)
@interface ViewController : UIViewController
- (void)viewController:(UIViewController *)vc callBack:(void(^)(NSString *name)) callBack;
@end
- 作為返回值
- (void(^)(int count))func {
return ^(int count) {
NSLog(@"返回");
};
}
作為返回值的情況我沒怎么用過,前兩種用法詳見 demo
Block 內(nèi)存問題
在Block中某個對象持有Block本身,而Block又持有該對象就會引起內(nèi)存泄漏(通常是引用self),這里介紹幾種解決循環(huán)引用的方法
- 我們最常用的 使用__weak typeOf(self)
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
- 使用 __block ClassName
__block XXViewController* blockSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blockSelf);
blkSelf = nil;//不能省略
};
self.blk();//該block必須執(zhí)行一次,否則還是內(nèi)存泄露
使用該方法解決內(nèi)存問題,一定要注意在block代碼塊內(nèi),使用完使用完__block變量后將其設(shè)為nil,并且該block必須至少執(zhí)行一次后,不存在內(nèi)存泄露
- 將在Block內(nèi)要使用到的對象(一般為self對象),以Block參數(shù)的形式傳入,Block就不會捕獲該對象,而將其作為參數(shù)使用,其生命周期系統(tǒng)的棧自動管理,不造成內(nèi)存泄露。
self.blk = ^(UIViewController *vc) {
NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);
附: Block內(nèi)修改外部變量的值 __block修飾符
__block int a = 0;
void (^foo)(void) = ^{
a = 1;
};
foo(); //這里,a的值被修改為1
__block保證了棧上和Block內(nèi)(通常在堆上)可以訪問和修改“同一個變量”,__block是如何實現(xiàn)這一功能的?
__block發(fā)揮作用的原理:將棧上用__block修飾的自動變量封裝成一個結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問和修改同一份數(shù)據(jù)。