Swift中轉(zhuǎn)義閉包示例詳解
前言
Swift 是一種非常強(qiáng)大的編程語(yǔ)言,是為 Apple 生態(tài)系統(tǒng)開(kāi)發(fā)應(yīng)用程序的首選;iOS、macOS、watchOS 和 tvOS。作為使用 Swift 編寫代碼的開(kāi)發(fā)人員,我們經(jīng)常使用閉包;語(yǔ)言的一個(gè)重要而重要的章節(jié)。
閉包不是初學(xué)者開(kāi)始的主題。然而,這是每個(gè)人都必須盡快了解的東西。有很多方面需要了解并了解它們的工作原理。在所有這些中,有一個(gè)特定的;轉(zhuǎn)義閉包和@escaping屬性。在這篇文章中,我將盡可能簡(jiǎn)單地解釋它們是什么以及它們可能帶來(lái)的附帶影響。
轉(zhuǎn)義與非轉(zhuǎn)義閉包
在談?wù)撧D(zhuǎn)義閉包時(shí),我們總是指作為函數(shù)或方法參數(shù)提供的閉包。一般來(lái)說(shuō),我們將提供給方法(或函數(shù))的閉包分為兩類:
- 在方法執(zhí)行完成之前調(diào)用的閉包。
- 在方法執(zhí)行完成后調(diào)用的閉包。
在后一種情況下,我們談?wù)摰氖寝D(zhuǎn)義閉包;關(guān)閉該繼續(xù)即使電子住后的的xecution方法,直到我們?cè)谝院蟮娜魏螘r(shí)間在未來(lái)給他們打電話。
在前一種情況下,與我上面描述的完全相反,我們稱閉包為non-escaping。
直到 Swift 3,默認(rèn)情況下,所有作為參數(shù)傳遞給方法或函數(shù)的閉包都被認(rèn)為是轉(zhuǎn)義的。自 Swift 3 以來(lái),這不再正確;默認(rèn)情況下,所有方法都被認(rèn)為是非轉(zhuǎn)義的,這意味著它們?cè)诜椒▓?zhí)行完成之前被調(diào)用。
以下代碼部分演示了一個(gè)非轉(zhuǎn)義閉包:
func add(num1: Double, num2: Double, completion: (_ result: Double) -> Void) { let sum = num1 + num2 completion(sum) }
所述completion封閉件之前執(zhí)行代碼葉調(diào)用的方法,所以這不是一個(gè)逸出閉合的情況。
然而,一個(gè)閉包是如何從一個(gè)方法中逃脫的,所以我們最終得到了與上述情況相反的結(jié)果?
逃離方法
為了使閉包成為轉(zhuǎn)義閉包,有必要將對(duì)其的引用保留在方法的范圍之外,以便我們稍后使用它??纯聪旅娴拇a:
class Demo { var result: Double? var resultHandler: (() -> Void)? func add2(num1: Double, num2: Double, completion: () -> Void) { resultHandler = completion result = num1 + num2 } }
這里我們有一個(gè)result屬性,它保存在方法內(nèi)部發(fā)生的加法的結(jié)果。但我們也resultHandler有財(cái)產(chǎn);this 保持對(duì)completion作為方法參數(shù)提供的閉包的引用。
閉包通過(guò)以下行從方法中轉(zhuǎn)義:
resultHandler = completion
然而,這不是唯一需要的操作,所以我們可以說(shuō)這completion是一個(gè)轉(zhuǎn)義閉包。我們必須明確指出編譯器,否則我們將在 Xcode 中看到以下錯(cuò)誤:
為了修復(fù)它,我們需要用@escaping屬性標(biāo)記閉包。我們將此屬性放在閉包名稱和分號(hào)之后,但在閉包類型之前,如下所示:
func add2(num1: Double, num2: Double, completion: @escaping () -> Void) { ... }
編譯器不再抱怨,completion現(xiàn)在正式成為轉(zhuǎn)義閉包。
將轉(zhuǎn)義關(guān)閉付諸行動(dòng)
讓我們?cè)谏厦娴腄emo類中再添加兩個(gè)方法;一個(gè)將調(diào)用add2(num1:num2:completion:)方法,另一個(gè)將調(diào)用resultHandler閉包以獲得最終結(jié)果:
class Demo { ... func doubleSum(num1: Double, num2: Double) { add2(num1: num1, num2: num2) { guard let result = self.result else { return } self.result = result * 2 } } func getResult() { resultHandler?() } }
第一種方法將 add 方法計(jì)算的結(jié)果加倍。但是,該結(jié)果不會(huì)翻倍,并且在add2(num1:num2:completion:)我們調(diào)用該getResult()方法之前,不會(huì)執(zhí)行該方法中閉包主體內(nèi)的代碼。
這是我們從轉(zhuǎn)義閉包中受益的地方;我們可以在我們的代碼中需要的時(shí)候以及在合適的時(shí)機(jī)觸發(fā)閉包的調(diào)用。盡管提供的示例故意過(guò)于簡(jiǎn)單,但在實(shí)際項(xiàng)目中,實(shí)際優(yōu)勢(shì)會(huì)變得更加明顯和大膽。
注意強(qiáng)參考周期
讓我們?yōu)镈emo類添加最后一個(gè),并實(shí)現(xiàn)默認(rèn)的初始化器和析構(gòu)器方法:
class Demo { init() { print("Init") } deinit { print("Deinit") } ... }
init()是Demo初始化實(shí)例時(shí)調(diào)用的第一個(gè)方法,deinit也是釋放實(shí)例之前調(diào)用的最后一個(gè)方法。我向它們都添加了一個(gè)打印命令,以驗(yàn)證它們是否被調(diào)用,并且在使用帶有上述轉(zhuǎn)義閉包的方法時(shí)沒(méi)有內(nèi)存泄漏。
注意:當(dāng)我們嘗試通過(guò)將對(duì)象設(shè)置為 nil 來(lái)釋放它時(shí),可能存在內(nèi)存泄漏,但該對(duì)象仍保留在內(nèi)存中,因?yàn)樵搶?duì)象與其他保持其活動(dòng)狀態(tài)的對(duì)象之間存在強(qiáng)引用。
現(xiàn)在,讓我們添加以下幾行來(lái)使用上述所有內(nèi)容:
var demo: Demo? = Demo() demo?.doubleSum(num1: 5, num2: 10) demo?.getResult() print((demo?.result!)!) demo = nil
首先,我們初始化類的一個(gè)可選實(shí)例Demo,以便稍后我們可以將其設(shè)為 nil。然后,我們調(diào)用該doubleSum(num1:num2:)方法以將作為參數(shù)給出的兩個(gè)數(shù)字相加,然后將該結(jié)果加倍。但是,正如我之前所說(shuō)的,在我們調(diào)用該getResult()方法之前不會(huì)發(fā)生這種情況;在方法中實(shí)際調(diào)用轉(zhuǎn)義閉包的那個(gè)add2(num1:num2:completion:)。
最后,我們打印實(shí)例中result屬性的值demo,并將其demo設(shè)為 nil。
*注意:*如上面的代碼片段所示,使用感嘆號(hào) (!) 強(qiáng)制展開(kāi)可選值是一種非常糟糕的做法,請(qǐng)不要這樣做。我在這里這樣做的唯一原因是為了讓事情盡可能簡(jiǎn)單。
以上行將打印以下內(nèi)容:
Init
30.0
請(qǐng)注意,此處缺少“Deinit”消息!也就是說(shuō)deinit沒(méi)有調(diào)用該方法,證明制作demo實(shí)例nil沒(méi)有實(shí)際結(jié)果??雌饋?lái),只需幾行簡(jiǎn)單的代碼,我們就設(shè)法解決了內(nèi)存泄漏問(wèn)題。
內(nèi)存泄漏背后的原因
在我們找到解決內(nèi)存泄漏的方法之前,有必要了解它發(fā)生的原因。為了找到它,讓我們退后幾步來(lái)修改我們之前所做的。
首先,我們使用以下completion行使閉包從方法中逃逸:
resultHandler = completion
這條線比看起來(lái)更“有罪”,因?yàn)樗鼊?chuàng)建了對(duì)閉包的強(qiáng)烈引用completion。
注意:閉包是引用類型,就像類一樣。
然而,僅憑這一點(diǎn)還不足以產(chǎn)生問(wèn)題,因?yàn)獒尫興emo實(shí)例會(huì)刪除對(duì)閉包的引用。真正的麻煩始于doubleSum(num1:num2:)方法內(nèi)部的閉包主體。
在那里,我們這次通過(guò)在使用對(duì)象訪問(wèn)屬性時(shí)捕獲**對(duì)象來(lái)創(chuàng)建另一個(gè)從閉包到demo實(shí)例的強(qiáng)引用:selfresult
guard let result = self.result else { return } self.result = result * 2
當(dāng)它們都到位時(shí),Demo 實(shí)例保持對(duì)閉包的強(qiáng)引用,而閉包則是對(duì)實(shí)例的強(qiáng)引用。這會(huì)創(chuàng)建一個(gè)保留循環(huán),也稱為強(qiáng)引用循環(huán)。發(fā)生這種情況時(shí),每個(gè)引用類型都會(huì)使另一個(gè)引用類型在內(nèi)存中保持活動(dòng)狀態(tài),因此它們最終都不會(huì)被釋放。
請(qǐng)注意,這僅發(fā)生在包含帶有轉(zhuǎn)義閉包的方法的類中。structs 的情況有所不同,因?yàn)樗鼈儾皇且枚侵殿愋?,并且顯式引用self不是強(qiáng)制性的。
消除強(qiáng)引用循環(huán)
有兩種方法可以避免強(qiáng)引用循環(huán),從而避免內(nèi)存泄漏。第一個(gè)是在我們調(diào)用閉包后手動(dòng)且顯式地釋放對(duì)閉包的引用:
func getResult() { resultHandler?() // Setting nil to resultHandler removes the reference to closure. resultHandler = nil }
第二種方法是在閉包的主體中弱**捕獲self實(shí)例:
func doubleSum(num1: Double, num2: Double) { add2(num1: num1, num2: num2) { [weak self] in guard let result = self?.result else { return } self?.result = result * 2 } }
[weak self] in在關(guān)閉打開(kāi)后查看添加。有了這個(gè),我們建立了對(duì) Demo 實(shí)例的弱引用,因此我們避免了保留循環(huán)。請(qǐng)注意,我們將self用作可選值,并在其后加上問(wèn)號(hào) (?) 符號(hào)。
沒(méi)有必要應(yīng)用這兩種更改以避免強(qiáng)引用循環(huán)。無(wú)論我們最終選擇哪一個(gè),從現(xiàn)在開(kāi)始,輸出也將包含“Deinit”消息。這意味著該demo對(duì)象變?yōu)?nil,并且我們不再有內(nèi)存泄漏。
Init
30.0
Deinit
概括
離開(kāi)這里需要帶上一件事,那就是在使用轉(zhuǎn)義閉包時(shí)要小心。無(wú)論您是實(shí)現(xiàn)自己的接受轉(zhuǎn)義閉包作為參數(shù)的方法,還是使用具有轉(zhuǎn)義閉包的 API,請(qǐng)始終確保不會(huì)以強(qiáng)引用循環(huán)結(jié)束。在開(kāi)發(fā)應(yīng)用程序時(shí),內(nèi)存泄漏是一個(gè)很大的“禁忌”,我們當(dāng)然不希望我們的應(yīng)用程序在某個(gè)時(shí)候崩潰或因此被系統(tǒng)終止。另一方面,不要猶豫使用轉(zhuǎn)義閉包;它們提供了可以產(chǎn)生更強(qiáng)大代碼的優(yōu)勢(shì)。
到此這篇關(guān)于Swift中轉(zhuǎn)義閉包的文章就介紹到這了,更多相關(guān)Swift轉(zhuǎn)義閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Swift并發(fā)系統(tǒng)并行運(yùn)行多個(gè)任務(wù)使用詳解
這篇文章主要為大家介紹了Swift并發(fā)系統(tǒng)并行運(yùn)行多個(gè)任務(wù)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06關(guān)于Swift 4.1中的Codable改進(jìn)詳解
這篇文章主要給大家介紹了關(guān)于Swift 4.1中的Codable改進(jìn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02Swift如何在應(yīng)用中添加圖標(biāo)更換功能的方法
本篇文章主要介紹了Swift如何在應(yīng)用中添加圖標(biāo)更換功能的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02SwiftUI?引導(dǎo)頁(yè)界面實(shí)現(xiàn)示例
這篇文章主要為大家介紹了SwiftUI?引導(dǎo)頁(yè)界面實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Swift編程中實(shí)現(xiàn)希爾排序算法的代碼實(shí)例
希爾排序是對(duì)插入排序的一種改進(jìn)版本,算法本身并不穩(wěn)定,存在優(yōu)化空間,這里我們來(lái)講一下希爾排序的大體思路及Swift編程中實(shí)現(xiàn)希爾排序算法的代碼實(shí)例2016-07-07