Swift 中 Closure 閉包 和 Objective-C Block 對比

一 ?前言:

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)系

參考文檔

http://blog.csdn.net/zm_yh/article/details/51469621

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸭限,一起剝皮案震驚了整個濱河市蜕径,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌败京,老刑警劉巖兜喻,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赡麦,居然都是意外死亡朴皆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門泛粹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遂铡,“玉大人,你說我怎么就攤上這事晶姊“墙樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钾怔。 經(jīng)常有香客問我碱呼,道長,這世上最難降的妖魔是什么宗侦? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任巍举,我火速辦了婚禮,結(jié)果婚禮上凝垛,老公的妹妹穿的比我還像新娘懊悯。我一直安慰自己,他們只是感情好梦皮,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布炭分。 她就那樣靜靜地躺著,像睡著了一般剑肯。 火紅的嫁衣襯著肌膚如雪捧毛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天让网,我揣著相機與錄音呀忧,去河邊找鬼。 笑死溃睹,一個胖子當著我的面吹牛而账,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播因篇,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泞辐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了竞滓?” 一聲冷哼從身側(cè)響起咐吼,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎商佑,沒想到半個月后锯茄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡茶没,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年肌幽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礁叔。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡牍颈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琅关,到底是詐尸還是另有隱情煮岁,我是刑警寧澤讥蔽,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站画机,受9級特大地震影響冶伞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜步氏,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一响禽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荚醒,春花似錦沟优、人聲如沸撩穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泡躯,卻和暖如春贮竟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背较剃。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工咕别, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人写穴。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓惰拱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親确垫。 傳聞我的和親對象是個殘疾皇子弓颈,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容