- 本文首發(fā)于我的個(gè)人博客:「程序員充電站」
- 文章鏈接:「?jìng)魉烷T」
本文用來(lái)介紹 iOS開(kāi)發(fā)中 『Blocks』的基本使用。通過(guò)本文您將了解到:
- 什么是 Blocks
- Blocks 變量語(yǔ)法
- Blocks 變量的聲明與賦值
- Blocks 變量截獲局部變量值特性
- 使用 __block 說(shuō)明符
- Blocks 變量的循環(huán)引用以及如何避免
文中 Demo 我已放在了 Github 上,Demo 鏈接:傳送門
1. 什么是 Blocks 谋币?
一句話總結(jié):Blocks 是帶有 局部變量 的 匿名函數(shù)(不帶名稱的函數(shù))华望。
Blocks 也被稱作 閉包、代碼塊玖像。展開(kāi)來(lái)講,Blocks 就是一個(gè)代碼塊,把你想要執(zhí)行的代碼封裝在這個(gè)代碼塊里丛塌,等到需要的時(shí)候再去調(diào)用较解。
下邊我們先來(lái)理解 局部變量、匿名函數(shù) 的含義赴邻。
1.1 局部變量
在 C 語(yǔ)言中印衔,定義在函數(shù)內(nèi)部的變量稱為 局部變量。它的作用域僅限于函數(shù)內(nèi)部姥敛, 離開(kāi)該函數(shù)后就是無(wú)效的奸焙,再使用就會(huì)報(bào)錯(cuò)。
int x, y; // x彤敛,y 為全局變量
int fun(int a) {
int b, c; //a与帆,b,c 為局部變量
return a+b+c;
}
int main() {
int m, n; // m臊泌,n 為局部變量
return 0;
}
從上邊的代碼中鲤桥,我們可以看出:
- 我們?cè)陂_(kāi)始位置定義了變量 x 和 變量 y。 x 和 y 都是全局變量渠概。它們的作用域默認(rèn)是整個(gè)程序茶凳,也就是所有的源文件,包括 .c 和 .h 文件播揪。
- 而我們?cè)?fun() 函數(shù)中定義了變量 a贮喧、變量 b、變量 c猪狈。它們的作用域是 fun() 函數(shù)箱沦。只能在 fun() 函數(shù)內(nèi)部使用,離開(kāi) fun() 函數(shù)就是無(wú)效的雇庙。
- 同理谓形,main() 函數(shù)中的變量 m、變量 n 也只能在 main() 函數(shù)內(nèi)部使用疆前。
1.2 匿名函數(shù)
匿名函數(shù)指的是不帶有名稱的函數(shù)寒跳。但是 C 語(yǔ)言中不允許存在這樣的函數(shù)。
在 C 語(yǔ)言中竹椒,一個(gè)普通的函數(shù)長(zhǎng)這樣子:
int fun(int a);
fun 就是這個(gè)函數(shù)的名稱童太,在調(diào)用的時(shí)候必須要使用該函數(shù)的名稱 fun 來(lái)調(diào)用。
int result = fun(10);
在 C 語(yǔ)言中胸完,我們還可以通過(guò)函數(shù)指針來(lái)直接調(diào)用函數(shù)书释。但是在給函數(shù)指針賦值的時(shí)候,同樣也是需要知道函數(shù)的名稱赊窥。
int (*funPtr)(int) = &fun;
int result = (*funPtr)(10);
而我們通過(guò) Blocks爆惧,可以直接使用函數(shù)择诈,不用給函數(shù)命名送悔。
2. Blocks 變量語(yǔ)法
我們使用
^
運(yùn)算符來(lái)聲明 Blocks 變量讶迁,并將 Blocks 對(duì)象主體部分包含在{}
中领跛,同時(shí),句尾加;
表示結(jié)尾叔收。
下邊來(lái)看一個(gè)官方的示例:
int multiplier = 7;
int (^ myBlock)(int)= ^(int num) {
return num * multiplier;
};
這個(gè) Blocks 示例中齿穗,myBlock 是聲明的塊對(duì)象,返回類型是 整型值饺律,myBlock 塊對(duì)象有一個(gè) 參數(shù)窃页,參數(shù)類型為整型值,參數(shù)名稱為 num复濒。myBlock 塊對(duì)象的 主體部分 為 return num * multiplier;
脖卖,包含在 {}
中。
參考上面的示例巧颈,我們可以將 Blocks 表達(dá)式語(yǔ)法表述為:
^ 返回值類型 (參數(shù)列表) { 表達(dá)式 };
例如畦木,我們可以寫出這樣的 Block 語(yǔ)法:
^ int (int count) { return count + 1; };
Blocks 規(guī)定可以省略好多項(xiàng)目。例如:返回值類型砸泛、參數(shù)列表十籍。如果用不到,都可以省略唇礁。
2.1 省略返回值類型:^ (參數(shù)列表) { 表達(dá)式 };
上邊的 Blocks 語(yǔ)法就可以寫為:
^ (int count) { return count + 1; };
表達(dá)式中勾栗,return 語(yǔ)句使用的是 count + 1
語(yǔ)句的返回類型。如果表達(dá)式中有多個(gè) return 語(yǔ)句盏筐,則所有 return 語(yǔ)句的返回值類型必須一致围俘。
如果表達(dá)式中沒(méi)有 return 語(yǔ)句,則可以用 void 表示琢融,或者也省略不寫界牡。代碼如下:。
^ void (int count) { printf("%d\n", count); }; // 返回值類型使用 void
^ (int count) { printf("%d\n", count); }; // 省略返回值類型
2.2 省略參數(shù)列表 ^ 返回值類型 (void) { 表達(dá)式 };
如果表達(dá)式中漾抬,沒(méi)有使用參數(shù)宿亡,則用 void 表示,也可以省略 void奋蔚。
^ int (void) { return 1; }; // 參數(shù)列表使用 void
^ int { return 1; }; // 省略參數(shù)列表類型
2.3 省略返回值類型她混、參數(shù)列表:^ { 表達(dá)式 };
從上邊 2.1 中可以看出烈钞,無(wú)論有無(wú)返回值泊碑,都可以省略返回值類型。并且毯欣,從 2.2 中可以看出馒过,如果不需要參數(shù)列表的話,也可以省略參數(shù)列表酗钞。則代碼可以簡(jiǎn)化為:
^ { printf("Blocks"); };
3. Blocks 變量的聲明與賦值
3.1 Blocks 變量的聲明與賦值語(yǔ)法
Blocks 變量的聲明與賦值語(yǔ)法可以總結(jié)為:
返回值類型 (^變量名) (參數(shù)列表) = Blocks 表達(dá)式
注意:此處返回值類型不可以省略腹忽,若無(wú)返回值来累,則使用 void 作為返回值類型。
例如窘奏,定義一個(gè)變量名為 blk 的 Blocks 變量:
int (^blk) (int) = ^(int count) { return count + 1; };
int (^blk1) (int); // 聲明變量名為 blk1 的 Blocks 變量
blk1 = blk; // 將 blk 賦值給 blk1
Blocks 變量的聲明語(yǔ)法有點(diǎn)復(fù)雜嘹锁,其實(shí)我們可以和 C 語(yǔ)言函數(shù)指針的聲明類比著來(lái)記。
Blocks 變量的聲明就是把聲明函數(shù)指針類型的變量
*
變?yōu)?^
着裹。
// C 語(yǔ)言函數(shù)指針聲明與賦值
int func (int count) {
return count + 1;
}
int (*funcptr)(int) = &func;
// Blocks 變量聲明與賦值
int (^blk) (int) = ^(int count) { return count + 1; };
3.2 Blocks 變量的聲明與賦值的使用
3.2.1 作為局部變量:返回值類型 (^變量名) (參數(shù)列表) = 返回值類型 (參數(shù)列表) { 表達(dá)式 };
我們可以把 Blocks 變量作為局部變量领猾,在一定范圍內(nèi)(函數(shù)、方法內(nèi)部)使用骇扇。
// Blocks 變量作為本地變量
- (void)useBlockAsLocalVariable {
void (^myLocalBlock)(void) = ^{
NSLog(@"useBlockAsLocalVariable");
};
myLocalBlock();
}
3.2.2 作為帶有 property 聲明的成員變量:@property (nonatomic, copy) 返回值類型 (^變量名) (參數(shù)列表);
作用類似于 delegate摔竿,實(shí)現(xiàn) Blocks 回調(diào)。
/* Blocks 變量作為帶有 property 聲明的成員變量 */
@property (nonatomic, copy) void (^myPropertyBlock) (void);
// Blocks 變量作為帶有 property 聲明的成員變量
- (void)useBlockAsProperty {
self.myPropertyBlock = ^{
NSLog(@"useBlockAsProperty");
};
self.myPropertyBlock();
}
3.2.3 作為 OC 方法參數(shù):- (void)someMethodThatTaksesABlock:(返回值類型 (^)(參數(shù)列表)) 變量名;
可以把 Blocks 變量作為 OC 方法中的一個(gè)參數(shù)來(lái)使用少孝,通常 blocks 變量寫在方法名的最后继低。
// Blocks 變量作為 OC 方法參數(shù)
- (void)someMethodThatTakesABlock:(void (^)(NSString *)) block {
block(@"someMethodThatTakesABlock:");
}
3.2.4 調(diào)用含有 Block 參數(shù)的 OC方法:[someObject someMethodThatTakesABlock:^返回值類型 (參數(shù)列表) { 表達(dá)式}];
// 調(diào)用含有 Block 參數(shù)的 OC方法
- (void)useBlockAsMethodParameter {
[self someMethodThatTakesABlock:^(NSString *str) {
NSLog(@"%@",str);
}];
}
通過(guò) 3.2.3 和 3.2.4 中,Blocks 變量作為 OC 方法參數(shù)的調(diào)用稍走,我們同樣可以實(shí)現(xiàn)類似于 delegate 的作用袁翁,即 Blocks 回調(diào)(后邊應(yīng)用場(chǎng)景中會(huì)講)。
3.2.5 作為 typedef 聲明類型:
typedef 返回值類型 (^聲明名稱)(參數(shù)列表);
聲明名稱 變量名 = ^返回值類型(參數(shù)列表) { 表達(dá)式 };
// Blocks 變量作為 typedef 聲明類型
- (void)useBlockAsATypedef {
typedef void (^TypeName)(void);
// 之后就可以使用 TypeName 來(lái)定義無(wú)返回類型钱磅、無(wú)參數(shù)列表的 block 了梦裂。
TypeName myTypedefBlock = ^{
NSLog(@"useBlockAsATypedef");
};
myTypedefBlock();
}
4. Blocks 變量截獲局部變量值特性
先來(lái)看一個(gè)例子。
// 使用 Blocks 截獲局部變量值
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
printf("a = %d, b = %d\n",a, b);
};
myLocalBlock(); // 打印結(jié)果:a = 10, b = 20
a = 20;
b = 30;
myLocalBlock(); // 打印結(jié)果:a = 10, b = 20
}
為什么兩次打印結(jié)果都是 a = 10, b = 20
盖淡?
明明在第一次調(diào)用 myLocalBlock();
之后已經(jīng)重新給變量 a年柠、變量 b 賦值了,為什么第二次調(diào)用 myLocalBlock();
的時(shí)候褪迟,使用的還是之前對(duì)應(yīng)變量的值冗恨?
因?yàn)?Block 語(yǔ)法的表達(dá)式使用的是它之前聲明的局部變量 a、變量 b味赃。Blocks 中掀抹,Block 表達(dá)式截獲所使用的局部變量的值,保存了該變量的瞬時(shí)值心俗。所以在第二次執(zhí)行 Block 表達(dá)式時(shí)傲武,即使已經(jīng)改變了局部變量 a 和 b 的值,也不會(huì)影響 Block 表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值城榛。
這就是 Blocks 變量截獲局部變量值的特性揪利。
5. 使用 __block 說(shuō)明符
實(shí)際上,在使用 Block 表達(dá)式的時(shí)候狠持,只能使用保存的局部變量的瞬時(shí)值疟位,并不能直接對(duì)其進(jìn)行改寫。直接修改編譯器會(huì)直接報(bào)錯(cuò)喘垂,如下圖所示甜刻。
那么如果绍撞,我們想要該寫 Block 表達(dá)式中截獲的局部變量的值,該怎么辦呢得院?
如果傻铣,我們想在 Block 表達(dá)式中,改寫 Block 表達(dá)式之外聲明的局部變量祥绞,需要在該局部變量前加上 __block
的修飾符矾柜。
這樣我們就能實(shí)現(xiàn):在 Block 表達(dá)式中,為表達(dá)式外的局部變量賦值就谜。
// 使用 __block 說(shuō)明符修飾怪蔑,更改局部變量值
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 打印結(jié)果:a = 20, b = 30
};
myLocalBlock();
}
可以看到,使用 __block 說(shuō)明符修飾之后丧荐,我們?cè)?Block表達(dá)式中缆瓣,成功的修改了局部變量值。
根據(jù)評(píng)論區(qū)增補(bǔ)一點(diǎn):如果 Blocks 截獲的是 Objective-C 對(duì)象虹统,例如 NSMutablearray 類對(duì)象弓坞,對(duì)該對(duì)象調(diào)用變更的方法是不會(huì)編譯報(bào)錯(cuò)的(例如調(diào)用
addObject:
方法)。但是如果對(duì)其調(diào)用賦值的方法车荔,則會(huì)編譯報(bào)錯(cuò)渡冻,就必須要加上 __block 說(shuō)明符進(jìn)行修飾了。
6. Blocks 變量的循環(huán)引用以及如何避免
從上文中我們知道 Block 會(huì)對(duì)引用的局部變量進(jìn)行持有忧便。同樣族吻,如果 Block 也會(huì)對(duì)引用的對(duì)象進(jìn)行持有,從而會(huì)導(dǎo)致相互持有珠增,引起循環(huán)引用超歌。
/* —————— retainCycleBlcok.m —————— */
#import <Foundation/Foundation.h>
#import "Person.h"
int main() {
Person *person = [[Person alloc] init];
person.blk = ^{
NSLog(@"%@",person);
};
return 0;
}
/* —————— Person.h —————— */
#import <Foundation/Foundation.h>
typedef void(^myBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) myBlock blk;
@end
/* —————— Person.m —————— */
#import "Person.h"
@implementation Person
@end
上面 retainCycleBlcok.m
中 main()
函數(shù)的代碼會(huì)導(dǎo)致一個(gè)問(wèn)題:person 持有成員變量 myBlock blk,而 blk 也同時(shí)持有成員變量 person蒂教,兩者互相引用巍举,永遠(yuǎn)無(wú)法釋放。就造成了循環(huán)引用問(wèn)題凝垛。
那么懊悯,如何來(lái)解決這個(gè)問(wèn)題呢?
6.1 ARC 下梦皮,通過(guò) __weak 修飾符來(lái)消除循環(huán)引用
在 ARC 下炭分,可聲明附有 __weak 修飾符的變量,并將對(duì)象賦值使用届氢。
int main() {
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blk = ^{
NSLog(@"%@",weakPerson);
};
return 0;
}
這樣欠窒,通過(guò) __weak覆旭,person 持有成員變量 myBlock blk退子,而 blk 對(duì) person 進(jìn)行弱引用岖妄,從而就消除了循環(huán)引用。
6.2 MRC 下寂祥,通過(guò) __block 修飾符來(lái)消除循環(huán)引用
MRC 下荐虐,是不支持 __weak 修飾符的。但是我們可以通過(guò) __block 來(lái)消除循環(huán)引用丸凭。
int main() {
Person *person = [[Person alloc] init];
__block typeof(person) blockPerson = person;
person.blk = ^{
NSLog(@"%@", blockPerson);
};
return 0;
}
通過(guò) __block 引用的 blockPerson福扬,是通過(guò)指針的方式來(lái)訪問(wèn) person,而沒(méi)有對(duì) person 進(jìn)行強(qiáng)引用惜犀,所以不會(huì)造成循環(huán)引用铛碑。
參考資料
- 書(shū)籍:『Objective-C 高級(jí)編程 iOS 與OS X 多線程和內(nèi)存管理』
- 博文:How Do I Declare A Block in Objective-C?
以上是 iOS 開(kāi)發(fā):『Blocks』詳盡總結(jié) (一)基本使用 的全部?jī)?nèi)容,可以用來(lái)了解 Block虽界,入門使用汽烦。下一篇我們通過(guò) Block 由 OC 代碼轉(zhuǎn)變的 C++ 源碼來(lái)抽絲剝繭的講一下 Block 的底層原理。
- 本文作者: 行走少年郎
- 本文鏈接: http://www.reibang.com/p/ab047cd47218
- 版權(quán)聲明: 本文章采用 CC BY-NC-SA 3.0 許可協(xié)議莉御。轉(zhuǎn)載請(qǐng)?jiān)谖淖珠_(kāi)頭注明『本文作者』和『本文鏈接』撇吞!