您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Swift中如何實現(xiàn)轉(zhuǎn)義閉包”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Swift中如何實現(xiàn)轉(zhuǎn)義閉包”這篇文章吧。
在談?wù)撧D(zhuǎn)義閉包時,我們總是指作為函數(shù)或方法參數(shù)提供的閉包。一般來說,我們將提供給方法(或函數(shù))的閉包分為兩類:
在方法執(zhí)行完成之前調(diào)用的閉包。
在方法執(zhí)行完成后調(diào)用的閉包。
在后一種情況下,我們談?wù)摰氖寝D(zhuǎn)義閉包;關(guān)閉該繼續(xù)即使電子住后的的xecution方法,直到我們在以后的任何時間在未來給他們打電話。
在前一種情況下,與我上面描述的完全相反,我們稱閉包為non-escaping。
直到 Swift 3,默認(rèn)情況下,所有作為參數(shù)傳遞給方法或函數(shù)的閉包都被認(rèn)為是轉(zhuǎn)義的。自 Swift 3 以來,這不再正確;默認(rèn)情況下,所有方法都被認(rèn)為是非轉(zhuǎn)義的,這意味著它們在方法執(zhí)行完成之前被調(diào)用。
以下代碼部分演示了一個非轉(zhuǎn)義閉包:
func add(num1: Double, num2: Double, completion: (_ result: Double) -> Void) { let sum = num1 + num2 completion(sum) }
所述completion封閉件之前執(zhí)行代碼葉調(diào)用的方法,所以這不是一個逸出閉合的情況。
然而,一個閉包是如何從一個方法中逃脫的,所以我們最終得到了與上述情況相反的結(jié)果?
為了使閉包成為轉(zhuǎn)義閉包,有必要將對其的引用保留在方法的范圍之外,以便我們稍后使用它??纯聪旅娴拇a:
class Demo { var result: Double? var resultHandler: (() -> Void)? func add2(num1: Double, num2: Double, completion: () -> Void) { resultHandler = completion result = num1 + num2 } }
這里我們有一個result屬性,它保存在方法內(nèi)部發(fā)生的加法的結(jié)果。但我們也resultHandler有財產(chǎn);this 保持對completion作為方法參數(shù)提供的閉包的引用。
閉包通過以下行從方法中轉(zhuǎn)義:
resultHandler = completion
然而,這不是唯一需要的操作,所以我們可以說這completion是一個轉(zhuǎn)義閉包。我們必須明確指出編譯器,否則我們將在 Xcode 中看到以下錯誤:
為了修復(fù)它,我們需要用@escaping屬性標(biāo)記閉包。我們將此屬性放在閉包名稱和分號之后,但在閉包類型之前,如下所示:
func add2(num1: Double, num2: Double, completion: @escaping () -> Void) { ... }
編譯器不再抱怨,completion現(xiàn)在正式成為轉(zhuǎn)義閉包。
讓我們在上面的Demo類中再添加兩個方法;一個將調(diào)用add2(num1:num2:completion:)方法,另一個將調(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 方法計算的結(jié)果加倍。但是,該結(jié)果不會翻倍,并且在add2(num1:num2:completion:)我們調(diào)用該getResult()方法之前,不會執(zhí)行該方法中閉包主體內(nèi)的代碼。
這是我們從轉(zhuǎn)義閉包中受益的地方;我們可以在我們的代碼中需要的時候以及在合適的時機觸發(fā)閉包的調(diào)用。盡管提供的示例故意過于簡單,但在實際項目中,實際優(yōu)勢會變得更加明顯和大膽。
讓我們?yōu)镈emo類添加最后一個,并實現(xiàn)默認(rèn)的初始化器和析構(gòu)器方法:
class Demo { init() { print("Init") } deinit { print("Deinit") } ... }
init()是Demo初始化實例時調(diào)用的第一個方法,deinit也是釋放實例之前調(diào)用的最后一個方法。我向它們都添加了一個打印命令,以驗證它們是否被調(diào)用,并且在使用帶有上述轉(zhuǎn)義閉包的方法時沒有內(nèi)存泄漏。
注意:當(dāng)我們嘗試通過將對象設(shè)置為 nil 來釋放它時,可能存在內(nèi)存泄漏,但該對象仍保留在內(nèi)存中,因為該對象與其他保持其活動狀態(tài)的對象之間存在強引用。
現(xiàn)在,讓我們添加以下幾行來使用上述所有內(nèi)容:
var demo: Demo? = Demo() demo?.doubleSum(num1: 5, num2: 10) demo?.getResult() print((demo?.result!)!) demo = nil
首先,我們初始化類的一個可選實例Demo,以便稍后我們可以將其設(shè)為 nil。然后,我們調(diào)用該doubleSum(num1:num2:)方法以將作為參數(shù)給出的兩個數(shù)字相加,然后將該結(jié)果加倍。但是,正如我之前所說的,在我們調(diào)用該getResult()方法之前不會發(fā)生這種情況;在方法中實際調(diào)用轉(zhuǎn)義閉包的那個add2(num1:num2:completion:)。
最后,我們打印實例中result屬性的值demo,并將其demo設(shè)為 nil。
*注意:*如上面的代碼片段所示,使用感嘆號 (!) 強制展開可選值是一種非常糟糕的做法,請不要這樣做。我在這里這樣做的唯一原因是為了讓事情盡可能簡單。
以上行將打印以下內(nèi)容:
Init
30.0
請注意,此處缺少“Deinit”消息!也就是說deinit沒有調(diào)用該方法,證明制作demo實例nil沒有實際結(jié)果??雌饋?,只需幾行簡單的代碼,我們就設(shè)法解決了內(nèi)存泄漏問題。
在我們找到解決內(nèi)存泄漏的方法之前,有必要了解它發(fā)生的原因。為了找到它,讓我們退后幾步來修改我們之前所做的。
首先,我們使用以下completion行使閉包從方法中逃逸:
resultHandler = completion
這條線比看起來更“有罪”,因為它創(chuàng)建了對閉包的強烈引用completion。
注意:閉包是引用類型,就像類一樣。
然而,僅憑這一點還不足以產(chǎn)生問題,因為釋放demo實例會刪除對閉包的引用。真正的麻煩始于doubleSum(num1:num2:)方法內(nèi)部的閉包主體。
在那里,我們這次通過在使用對象訪問屬性時捕獲**對象來創(chuàng)建另一個從閉包到demo實例的強引用:selfresult
guard let result = self.result else { return } self.result = result * 2
當(dāng)它們都到位時,Demo 實例保持對閉包的強引用,而閉包則是對實例的強引用。這會創(chuàng)建一個保留循環(huán),也稱為強引用循環(huán)。發(fā)生這種情況時,每個引用類型都會使另一個引用類型在內(nèi)存中保持活動狀態(tài),因此它們最終都不會被釋放。
請注意,這僅發(fā)生在包含帶有轉(zhuǎn)義閉包的方法的類中。structs 的情況有所不同,因為它們不是引用而是值類型,并且顯式引用self不是強制性的。
有兩種方法可以避免強引用循環(huán),從而避免內(nèi)存泄漏。第一個是在我們調(diào)用閉包后手動且顯式地釋放對閉包的引用:
func getResult() { resultHandler?() // Setting nil to resultHandler removes the reference to closure. resultHandler = nil }
第二種方法是在閉包的主體中弱**捕獲self實例:
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)閉打開后查看添加。有了這個,我們建立了對 Demo 實例的弱引用,因此我們避免了保留循環(huán)。請注意,我們將self用作可選值,并在其后加上問號 (?) 符號。
沒有必要應(yīng)用這兩種更改以避免強引用循環(huán)。無論我們最終選擇哪一個,從現(xiàn)在開始,輸出也將包含“Deinit”消息。這意味著該demo對象變?yōu)?nil,并且我們不再有內(nèi)存泄漏。
Init
30.0
Deinit
離開這里需要帶上一件事,那就是在使用轉(zhuǎn)義閉包時要小心。無論您是實現(xiàn)自己的接受轉(zhuǎn)義閉包作為參數(shù)的方法,還是使用具有轉(zhuǎn)義閉包的 API,請始終確保不會以強引用循環(huán)結(jié)束。在開發(fā)應(yīng)用程序時,內(nèi)存泄漏是一個很大的“禁忌”,我們當(dāng)然不希望我們的應(yīng)用程序在某個時候崩潰或因此被系統(tǒng)終止。另一方面,不要猶豫使用轉(zhuǎn)義閉包;它們提供了可以產(chǎn)生更強大代碼的優(yōu)勢。
以上是“Swift中如何實現(xiàn)轉(zhuǎn)義閉包”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。