一 ?前言:
Swift 中的 Closure 和 Objective-C 中的Block ?都是非常常用 的語法虾啦。本文從定義 和 使用兩方面试伙,對外部變量的捕獲等 各方面對比二者的異同和優(yōu)劣颂鸿。
二 對比:
代碼下載地址:?https://github.com/LoveHouLinLi/CompareBlockClosure? ? 這里面有兩個工程 一個是Objective-C 寫的 另一個是 Swift 寫的 妥凳。 用于區(qū)分二者的不同拇囊。 我們分別打開兩個工程幻赚。
1.0 ? ?定義 開始
OC 中: 下面這是一個 Block的 簡單定義 ? ?返回值 (^ Block名稱)(參數(shù)) ?赊窥,參數(shù)可以是多個?
但是 Block 類型的并不能作為 返回值 也不可以 作為函數(shù)的返回值 爆惧。 在swift 中Closure 是可以的作為另一個閉包 也可以作為函數(shù)的返回值 。?
void (^removeBlock)(void) = ^{
? ? ? ? [array removeObjectAtIndex:3];
? ? };
但是實際上 我們使用block ?都是 使用宏定義的方式锨能。 這樣很方便我們使用 不用到處寫上復(fù)雜的表達式了扯再。
typedef void(^Block)(int x);
很多 朋友 好奇為啥 OC 中Block 要用copy 修飾。 其實 Block 用Copy修飾 源自于 MRC 時期址遇;
在 MRC ?時代請看下面這段代碼: 會引起 crash ?因為 MRC 中Block 默認是在棧上面的熄阻。?
void (^block)(void);
- (void)testStackBlock
{
? ? int? i=arc4random()%2;
? ? if (i==0) {
? ? ? ? block=^{
? ? ? ? ? ? NSLog(@" Block A i is %d",i);
? ? ? ? };
? ? }else{
? ? ? ? block=^{
? ? ? ? ? ? NSLog(@"Block B i is %d",i);
? ? ? ? };
? ? }
? ? ? ?block();
}
如果想讓 上面那段代碼不 Crash ?就要這樣寫,每次賦予新值的時候這樣寫。 這樣Block 從棧 copy至堆上面了倔约。
block=[^{
? ? ? ? ? ? NSLog(@" Block A i is %d",i);
? ? ? ? } copy];
但是 在ARC 時代 默認Block 就是在堆上面 秃殉,所以不需要copy 但是大家習慣上這樣修飾了。?
Swift 中:
Closure 是這樣定義的:(參數(shù)) -> 返回值 ?這種語法 運算的表達式?
下面的每段都是可以的?
let calAdd:(Int,Int) ->(Int) ={ (a:Int,b:Int) -> Int in return a + b }
let calAdd4:(Int,Int) ->(Int) = {
? ? ? ? ? ? (a,b) in return a+b;
? ? ? ? }
let calAdd3:(Int,Int) ->(Int) = {
? ? ? ? ? ? (a,b) in a+b;
? ? ? ? }
let calAdd2:(Int,Int) ->(Int) = {
? ? ? ? ? ? a,b in? return a+b
? ? ? ? }
let calAdd6:(Int,Int) ->(Int) = {
? ? ? ? ? ? a,b in a+b
? ? ? ? }
如果閉包沒有參數(shù)浸剩,可以直接省略“in”
? ? ? ? let calAdd5:() ->Int = {
? ? ? ? ? ? return 100+150;
? ? ? ? }
同樣 Closure ?也可以使用 宏定義 ?也能方便我們使用?
typealias AddClosure = (Int,Int) ->(Int)
下面是 Closure 作為函數(shù)的返回值 的情形:
? ? func captureValue2(sums amount:Int) -> ()->Int {
? ? ? ? var total = 0
? ? ? ? let AddBlock:() ->Int = {
? ? ? ? ? ? total += amount
? ? ? ? ? ? return total
? ? ? ? }
? ? ? ? return AddBlock
? ? }
若將閉包作為函數(shù)最后一個參數(shù)钾军,可以省略參數(shù)標簽,然后將閉包表達式寫在函數(shù)調(diào)用括號后面
? ? func trailingClosure(testBlock:()->Void) {
? ? ? ? testBlock()? // 調(diào)用block
? ? }
? ? ? ? trailingClosure(testBlock: {
? ? ? ? ? ? print("正常寫法 沒省略()")? // 和 OC 中的Block 寫法類似
? ? ? ? })
2.0 ?從捕獲外部的變量角度分析?
不論是 Block 和 Swift ?都可以捕獲外部的變量。
OC 中的 Block 會從 局部非指針變量 绢要,局部指針變量 吏恭,全局變量 ,static 變量 四個方面來對比重罪。
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ?//? 這種在 Swift 里面叫做自動閉包
? ? array = @[@"I",@"have",@"a",@"apple"].mutableCopy;
? ? [self testBlockCaptureStaticValue];
? ? [self testBlockCaptureGlobalNormalBasicValue];
? ? [self testBlockCapturePartNormalBasicValue];
? ? [self testBlockChangeCapturedNormalTypeWithPointer];
}
DEMO ?中分別對比了這幾種類型 樱哼。 注意指針類型局部變量 為啥使用指針能改變 ?不適用指針沒改變 。在OC block 中有循環(huán)應(yīng)用的情況剿配。?
在 swift 中 捕獲的變量 和 OC 中有很大不同搅幅。
首先是 逃逸閉包
當一個閉包作為參數(shù)傳到一個函數(shù)中,需要這個閉包在函數(shù)返回之后才被執(zhí)行呼胚,我們就稱該閉包從函數(shù)種逃逸茄唐。一般如果閉包在函數(shù)體內(nèi)涉及到異步操作,但函數(shù)卻是很快就會執(zhí)行完畢并返回的砸讳,閉包必須要逃逸掉琢融,以便異步操作的回調(diào)界牡。
?? ? 逃逸閉包一般用于異步函數(shù)的回調(diào),比如網(wǎng)絡(luò)請求成功的回調(diào)和失敗的回調(diào)漾抬。語法:在函數(shù)的閉包行參前加關(guān)鍵字“@escaping”宿亡。
Block 中默認都是逃逸的
func doSomethingDelayWithNoneEscaping(some:()->Void) {
? ? ? ? DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
? ? ? ? ? ? some()
? ? ? ? }
//? ? ? IDE 編譯優(yōu)化? 這種顯式的我們能避免? 我們在 TestViewController
? ? ? ? some()
? ? ? ? print("do Something Function Body")
? ? }
?這種會有類似的提醒 編譯器? 提醒加 escaping ?IDE 提醒如下:
? ? ? Closure use of non-escaping parameter 'some' may allow it to escape
將一個閉包標記為@escaping意味著你必須在閉包中顯式的引用self。? ?其實@escaping和self都是在提醒你纳令,這是一個逃逸閉包挽荠,? 別誤操作導(dǎo)致了循環(huán)引用!而非逃逸包可以隱式引用self平绩。 ?在Block 中為了避免循環(huán)引用我們在使用完Block后 置block 為nil 這樣 避免圈匆。 非逃逸閉包默認幫我們做了這一步。在返回時 把Closure 設(shè)置為空捏雌。?在 TestViewController 中做了對比跃赚。
注意 局部非指針變量時 ?和 Closure的 區(qū)別!P允! Block 中沒改變 ?Closure 中改變了
- (void)testBlockCapturePartNormalBasicValue
{
? ? int num = 100;
? ? int (^TestBlock)(int) = ^(int x){
? ? ? ? return num+x;
? ? };
? ? NSLog(@"使用局部變量的結(jié)果:%d",TestBlock(100));
? ? num=50;//change the value of number.
? ? NSLog(@"修改局部變量的值再次調(diào)用block的結(jié)果:%d",TestBlock(100));
? ? // 200 200
}
但是在 Closure 中 局部變量發(fā)生了改變肤频。?
var num:Int = 100
? ? //? ? static var b:Int = 100
? ? func? testClosureCaptureStaticValue()? {
? ? ? ? var number:Int = 100
? ? ? ? let closure:(Int) ->(Int) = {
? ? ? ? ? ? a in a+number
? ? ? ? }
? ? ? ? print("局部變量改變number值\(closure(100))")
? ? ? ? number = 50
? ? ? ? print("局部變量改變number值\(closure(100))")
? ? ? ?let closure2:(Int) ->(Int) = {
? ? ? ? ? ? a in a+self.num
? ? ? ? }
? ? ? ?print("全局變量改變number值\(closure2(100))")
? ? ? ? num = 50
? ? ? ? print("全局變量改變number值\(closure2(100))")
? ? }
在Swift中,這叫做截獲變量的引用汁雷。閉包默認會截取變量的引用侠讯,也就是說所有變量默認情況下都是加了__block修飾符的 ?這點和 Block 不同。
3.0 ? 循環(huán)引用
OC 中的循環(huán)引用 請參考 我http://blog.csdn.net/delongyangforcsdn/article/details/74529926? ?和 http://www.reibang.com/writer#/notebooks/20125367/notes/20921257??的內(nèi)容 這里就 不多說了少孝。
但是在 swift 中 原理 是類似的 但是 ?因為 Swift 中可選類型的存在 導(dǎo)致 情形多出了些继低。請看代碼 Swift 工程中的TestViewController 。
先來看第一種形式的 循環(huán)引用稍走。?
這個 block 是一個全局的 ?block 沒有說是escaping 還是 非escaping ?這點 和block 中的類似 袁翁。
var block:(()->())?
func testRetainCycleInClosureThree() {
? ? ? ? let a = Person()
? ? ? ? // 全局 的 變量
? ? ? ? block = {
? ? ? ? ? ? print(self.view.frame)
? ? ? ? }
? ? ? ? a.name = "New Name"
? ? ? ? block!()
? ? }
現(xiàn)在 看第二種形式的 ?循環(huán)引用 這種形式 。關(guān)鍵是 ??person = Person() 是一個全局變量 婿脸。 controller 雖然不直接持有 closure 但是 person的 block 持有了Closure ?而Controller 持有了person 粱胜。 而 Closure 又持有Controller。 ?
func testRetainCycleInClosureFour() {
? ? ? ? person = Person()
? ? ? ? let block = {
? ? ? ? ? ? self.x = 20;
? ? ? ? ? ? print(self.x)
? ? ? ? }
? ? ? ? person?.block = block
//? ? ? ? person = nil? // 如果person 不設(shè)置成 nil 會有循環(huán)引用
? ? ? ? block()
? ? }
在 OC 中如果這樣寫 也會造成 循環(huán)引用?
- (void)testCycleFour
{
? ? self.person = [[Person alloc] initWithName:@"name"];
? ? void (^block)(void) = ^(){
? ? ? NSLog(@"rect is %@",NSStringFromCGRect(self.view.frame));
? ? };
? ? self.person.block2 = block;
}
我們先創(chuàng)建了可選類型的變量a狐树,然后創(chuàng)建一個閉包變量焙压,并把它賦值給a的block屬性。這個閉包內(nèi)部又會截獲a,那這樣是否會導(dǎo)致循環(huán)引用呢涯曲? ? ? 答案是否定的野哭。雖然從表面上看,對象的閉包屬性截獲了對象本身幻件。但是如果你運行上面這段代碼拨黔,你會發(fā)現(xiàn)對象的deinit方法確實被調(diào)用了,打印結(jié)果不是“A”而是“nil”绰沥。
? ? 這是因為我們忽略了可選類型這個因素篱蝇。這里的a不是A類型的對象,而是一個可選類型變量徽曲,其內(nèi)部封裝了A的實例對象零截。閉包截獲的是可選類型變量a,當你執(zhí)行a = nil時秃臣,并不是釋放了變量a涧衙,而是釋放了a中包含的A類型實例對象。所以A的deinit方法會執(zhí)行甜刻,當你調(diào)用block時绍撞,由于使用了可選鏈,就會得到nil得院,如果使用強制解封,程序就會崩潰章贞。
注意 O榻省!這里循環(huán)的原因 是?person 是全局的 和 是否調(diào)用沒有關(guān)系
參考文檔