前言
1、理解
Block其實(shí)就是一個(gè)代碼塊。本質(zhì)上來(lái)說(shuō),一個(gè)Block就是一段能夠在將來(lái)被執(zhí)行的代碼碴巾。然而Block又是一個(gè)普通的Objective-C對(duì)象,正因?yàn)樗菍?duì)象,Block可以被作為參數(shù)傳遞,可以作為返回值從一個(gè)方法返回,可以用來(lái)給變量賦值丑搔。
2厦瓢、特點(diǎn)
將代碼放在Block中,使代碼更簡(jiǎn)潔緊湊,易于閱讀, 而且比函數(shù)使用更方便、更美觀啤月。Block其實(shí)是對(duì)閉包的實(shí)現(xiàn)煮仇。
3、Block的優(yōu)勢(shì)
在Block之前,如果我們想要調(diào)用一段代碼,然后之后一段時(shí)間,讓它給我們返回,我們一般會(huì)使用delegate或者NSNotification谎仲。但是使用過(guò) delegate 和 NSNotification 大家就應(yīng)該會(huì)感覺(jué)到——我們會(huì)不可避免的將代碼寫的到處都是,我們需要在某處開始一個(gè)任務(wù),在另外一個(gè)地方來(lái)處理這個(gè)返回結(jié)果浙垫。使用 Block 就可以在一定程度上避免這個(gè)問(wèn)題。
下面進(jìn)入主題
Block是什么
Block : 帶有自動(dòng)變量(局部變量)的匿名函數(shù)
匿名函數(shù) :沒(méi)有函數(shù)名的函數(shù)郑诺,一對(duì){}包裹的內(nèi)容是匿名函數(shù)的作用域
自動(dòng)變量:棧上聲明的一個(gè)變量不是靜態(tài)變量和全局變量夹姥,是不可以在這個(gè)棧內(nèi)聲明的匿名函數(shù)中使用的,但在Block中卻可以辙诞。
關(guān)于帶有自動(dòng)變量的含義辙售,這是因?yàn)锽lock有捕獲外部變量的功能。能夠保存外部變量的瞬間值飞涂,所以即便在block外修改變量的值旦部,也不會(huì)對(duì)Block截獲的自動(dòng)變量的值產(chǎn)生影響。
例 一道比較經(jīng)典的面試題:
int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n",val);
};
val = 2;
blk();
我們都知道這段代碼 輸出val的值為 10 而不是2较店,這是因?yàn)锽lock截獲變量并保存變量的瞬間值
Block 語(yǔ)法
- Block的聲明及定義
//返回值(^Blok名字)(參數(shù)列表) = ^返回值(參數(shù)列表) {實(shí)現(xiàn)};
//標(biāo)準(zhǔn)的定義和聲明
int(^blk)(int count) = ^int(int count) {
return count++;
};
blk(1);
//當(dāng)返回值為void 實(shí)現(xiàn)部分可以忽略void不寫 即 ^(int count){}
void(^blk1)(int count) = ^void(int count) {
count++;
};
blk1(1);
//當(dāng)參數(shù)為void 實(shí)現(xiàn)部分可以忽略參數(shù)不寫 即 ^int{}
int(^blk2)(void) = ^int(void) {
return 1;
};
blk2();
//當(dāng)參數(shù)和返回值都為void 實(shí)現(xiàn)部分可以簡(jiǎn)寫為 ^{}
void(^blk3)(void) = ^void(void) {
NSLog(@"參數(shù)和返回值都為void");
};
blk3();
//匿名Block 只有實(shí)現(xiàn)部分 沒(méi)有函數(shù)名
// ^int(int count) {
// return count;
// };
- typedef簡(jiǎn)化Block的聲明
//typedef 定義block 返回值(^Blok名字)(參數(shù)列表)
typedef void(^blk)(void); //無(wú)返回值 無(wú)參數(shù)
typedef void(^blk1)(NSString *name);//無(wú)返回值 有參數(shù)
typedef int(^blk2)(void);//有返回值 無(wú)參數(shù)
typedef int(^blk3)(int count);//有返回值 有參數(shù)
Block類型
根據(jù)Block在內(nèi)存中存儲(chǔ)的位置分為三種類型:
- 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時(shí) 該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(@"返回");
};
}
作為返回值的情況我沒(méi)怎么用過(guò)士八,前兩種用法詳見(jiàn) demo
Block 內(nèi)存問(wèn)題
在Block中某個(gè)對(duì)象持有Block本身,而Block又持有該對(duì)象就會(huì)引起內(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)存問(wèn)題,一定要注意在block代碼塊內(nèi)捧杉,使用完使用完__block變量后將其設(shè)為nil陕见,并且該block必須至少執(zhí)行一次后秘血,不存在內(nèi)存泄露
- 將在Block內(nèi)要使用到的對(duì)象(一般為self對(duì)象),以Block參數(shù)的形式傳入评甜,Block就不會(huì)捕獲該對(duì)象灰粮,而將其作為參數(shù)使用,其生命周期系統(tǒng)的棧自動(dò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)(通常在堆上)可以訪問(wèn)和修改“同一個(gè)變量”佩研,__block是如何實(shí)現(xiàn)這一功能的柑肴?
__block發(fā)揮作用的原理:將棧上用__block修飾的自動(dòng)變量封裝成一個(gè)結(jié)構(gòu)體,讓其在堆上創(chuàng)建旬薯,以方便從棧上或堆上訪問(wèn)和修改同一份數(shù)據(jù)晰骑。