Swift中的捕獲列表:強引用,弱引用,無主引用之間的區別

Swift中的捕獲列表:強引用,弱引用,無主引用之間的區別

捕獲列表位于代碼中的閉包參數列表之前,并將環境中的值捕獲為強,弱或無主。我們經常使用它們,主要是為了避免循環引用

首先,看一下這個問題:

class Singer {
    func playSong() {
        print("Shake is off!")
    }
}

創建一個方法,方法中實例化Singer,并使用Singer實例的playSong()創建一個閉包,并返回這個閉包以供其他地方使用

func sing() -> () -> Void {
    let taylor = Singer()
    
    let singing = {
        taylor.playSong()
        return
    }
    return singing
}

最后,我們可以調用sing()來獲取我們可以在任何想要打印playSong()的地方調用的函數

let singFunction = sing()
singFunction()      //打印出 Shake is off!

強引用

除非你要求特別的東西,否則Swift會使用強大的捕獲功能。這意味著閉包將捕獲閉包內使用的任何外部值,并確保它們永遠不會被破壞

func sing() -> () -> Void {
    let taylor = Singer()
    
    let singing = {
        taylor.playSong()
        return
    }
    return singing
}

那個taylor常量是在sing()函數內部進行的,所以通常它會在函數結束時被銷毀。但是,它會在閉包內部使用,這意味著只要閉包存在某個地方,即使函數返回后,Swift也會自動確保它保持活動狀態。

這是一個強大的捕獲行動。如果Swift允許taylor被摧毀,那么關閉將不再是安全的 - 它的taylor.playSong()方法將不再有效

弱引用

Swift讓我們指定一個捕獲列表來確定如何捕獲閉包內使用的值。與強引用常見使用的是弱引用,它改變了兩件事:

1.閉包不能保持弱捕獲的值,因此它們可能會被破壞并設置為nil

2.作為1的結果,弱引用值在Swift中始終是可選的。這會阻止你假設它們存在,而實際上它們可能不存在。

func sing() -> () -> Void {
    let taylor = Singer()

    let singing = { [weak taylor] in
        taylor?.playSong()
        return
    }

    return singing
}

[weak taylor]是我們的捕獲列表.它是閉包的一個特定部分,我們就如何捕獲值給出具體指示,在這里我們說taylor應該被弱引用,這就是為什么我們需要使用taylor?.playSong() - 它現在是可選的,因為它可以在任何時候設置為nil

方法修改以后,調用singFunction()會發現不會打印任何東西,原因是taylor只存在于sing()中,因為它返回的閉包并沒有強持有它。

如果上面代碼修改成:

func sing() -> () -> Void {
    let taylor = Singer()

    let singing = { [weak taylor] in
        taylor!.playSong()
        return
    }

    return singing
}

會引發崩潰,因為taylornil

無主引用

weak對應的是unowned,其行為更像是隱式解包可選項。就像弱引用一樣,無主引用允許值在未來的任何時候都變為nil。但是,您可以使用它們,就像它們總是在那里一樣 --你不需要解包可選項

func sing() -> () -> Void {
    let taylor = Singer()

    let singing = { [unowned taylor] in
        taylor.playSong()
        return
    }

    return singing
}

這里會發生崩潰,與我們之前強制解包類似:unowned taylor 告訴我們taylor會一直存在閉包的生命周期內,但實際上,taylor幾乎立即被銷毀,所以會崩潰

常見問題

當使用閉包捕獲時,人們常常遇到四個問題:

  1. 當閉包接受參數時,他們不確定在何處使用捕獲列表。
  2. 它們會產生循環引用,導致內存被消耗。
  3. 他們無意中使用強引用,特別是在使用多個捕獲時。
  4. 他們復制閉包,并共享捕獲的數據。

讓我們通過簡單的代碼,看看都會發生什么

捕獲列表與參數在一起

當你第一次開始使用捕獲列表時,這是一個常見的問題,但幸運的是,Swift為我們做了規定。

當一起使用捕獲列表和閉包參數時,捕獲列表必須始終首先出現,然后單詞in來標記閉包體的開始 - 嘗試將其放在閉包參數之后將阻止代碼編譯。

writeToLog { [weak self] user, message in 
    self?.addToLog("\(user) triggered event: \(message)")
}

循環引用

當A持有B,同時B持有A,稱之為循環引用

創建一個House類,有一個屬性(閉包),一個方法,一個銷毀器,當銷毀時打印一條消息

class House {
    var ownerDetails: (() -> Void)?

    func printDetails() {
        print("This is a great house.")
    }

    deinit {
        print("I'm being demolished!")
    }
}

這有一個類似的類Owner,不同的是屬性(閉包)

class Owner {
    var houseDetails: (() -> Void)?

    func printDetails() {
        print("I own a house.")
    }

    deinit {
        print("I'm dying!")
    }
}

我們在doblock中創建兩個類的示例,我們不需要catch,確保在}之前會銷毀

print("Creating a house and an owner")

do {
    let house = House()
    let owner = Owner()
}

print("Done")

//Creating a house and an owner
//I’m dying!
//I'm being demolished!
//Done

現在我們創建一個循環引用

print("Creating a house and an owner")

do {
    let house = House()
    let owner = Owner()
    house.ownerDetails = owner.printDetails
    owner.houseDetails = house.printDetails
}

print("Done")
//Creating a house and an owner
//Done

銷毀器中的信息不會被打印,這是因為 house有一個屬性指向owner,同時owner有一個屬性指向house,所以他們不能被安全銷毀,會導致內存不能釋放,稱之為內存泄露,會降低系統性能甚至應用終止

為解決這個問題,我們需要創建一個新的閉包,使用弱引用

print("Creating a house and an owner")

do {
    let house = House()
    let owner = Owner()
    house.ownerDetails = { [weak owner] in owner?.printDetails() }
    owner.houseDetails = { [weak house] in house?.printDetails() }
}

print("Done")

沒有必要將這兩個值都使用弱引用 - 重要的是至少有一個值,因為它允許Swift在必要時銷毀它們。

意外強引用

Swift默認為強引用,這可能會導致無意中的問題

func sing() -> () -> Void {
    let taylor = Singer()
    let adele = Singer()

    let singing = { [unowned taylor, adele] in
        taylor.playSong()
        adele.playSong()
        return
    }

    return singing
}

現在我們有兩個值被閉包捕獲,并且兩個值在閉包內使用方式相同,但是,只有taylor被無主引用,--adele被強引用,因為unowned關鍵字被使用必須放在每個捕獲值前面

如果要兩個值都無主引用

[unowned taylor, unowned adele]

Swift確實提供了一些防止意外捕獲的措施,但是有限的:如果你在一個閉包中使用self,Swift會強制你選擇使用self.或者self?讓你的意圖更清晰

在Swift中使用隱式self會發生很多事,比如,這個初始化程序調用playSong(),真正意義是self.playSong()---self由上下文隱含

class Singer {
    init() {
        playSong()
    }

    func playSong() {
        print("Shake it off!")
    }
}

swift不會讓你在閉包內使用隱含self,有助于減少循環引用

閉包復制

閉包會和它的復制閉包共享捕獲數據

var numberOfLinesLogged = 0

let logger1 = {
    numberOfLinesLogged += 1
    print("Lines logged: \(numberOfLinesLogged)")
}

logger1()

let logger2 = logger1
logger2()
logger1()
logger2()

//1
//2
//3
//4

什么時候使用強引用,什么時候使用弱引用,什么時候使用無主引用

  1. 如果你確定你的捕獲值永遠不會消失,而閉包有可能被調用,你可以使用unowned無主引用。這實際上只適用于weak會導致使用煩惱的少數幾次,但可以在閉包內使用guard let修飾弱引用變量
  2. 如果有循環引用的情況,那么兩個中的一個應該使用弱引用,通常是首先被銷毀的一個,所以如果視圖控制器A呈現視圖控制器B,則視圖控制器B可能將弱引用持有A。
  3. 如果不會發生循環引用,你可以使用強引用,例如執行動畫不會在動畫閉包內引起循環引用.所以你可以使用強引用

原文鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容