溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Swift中如何實現(xiàn)轉(zhuǎn)義閉包

發(fā)布時間:2021-11-04 09:07:52 來源:億速云 閱讀:163 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“Swift中如何實現(xiàn)轉(zhuǎn)義閉包”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Swift中如何實現(xiàn)轉(zhuǎn)義閉包”這篇文章吧。

轉(zhuǎn)義與非轉(zhuǎn)義閉包

在談?wù)撧D(zhuǎn)義閉包時,我們總是指作為函數(shù)或方法參數(shù)提供的閉包。一般來說,我們將提供給方法(或函數(shù))的閉包分為兩類:

  1. 在方法執(zhí)行完成之前調(diào)用的閉包。

  2. 在方法執(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 中看到以下錯誤:

Swift中如何實現(xiàn)轉(zhuǎn)義閉包

為了修復(fù)它,我們需要用@escaping屬性標(biāo)記閉包。我們將此屬性放在閉包名稱和分號之后,但在閉包類型之前,如下所示:

func  add2(num1:  Double,
          num2:  Double,
          completion:  @escaping  ()  ->  Void)  {
    ...
}

編譯器不再抱怨,completion現(xiàn)在正式成為轉(zhuǎn)義閉包。

將轉(zhuǎn)義關(guā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)存泄漏背后的原因

在我們找到解決內(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)

有兩種方法可以避免強引用循環(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è)資訊頻道!

向AI問一下細(xì)節(jié)

免責(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)容。

AI