swift 語言的基礎 - Strong, Weak and Unowned

  1. Strong, Weak and Unowned - 介紹
    1. Manual Reference Count - (MRC) / Automatic Reference Counting - (ARC)
  2. 比較表
    1. 例外?

Strong, Weak and Unowned - 介紹

Manual Reference Count - (MRC) / Automatic Reference Counting - (ARC)

這篇在介紹swift裡記憶體的管理機制

早前的手機,因為硬體資源的限制,對於記憶體的使用格外的珍惜,早期在 Objective-C / Xcode 4 的年代,每個變數的引用與操作 alloc、new、retain、release 都要手動的一個個計數,在撰寫程式時,以良好的習慣,儘可能地釋放出更多記憶體供程式運行。當然,萬一呼叫到已經釋放掉的變數,就直接 Crash,不好好釋放的話,或是變數之間互相引用,造成循環引用,而導致變數在需要被釋放時被咬住而無法釋放(Retain Cycle), 一旦記憶體堆疊到不夠用了 (Memory leak),也是直接 Crash。

隨著硬體的進步,Apple 也慢慢將手動參考計數 轉往自動參考計數,慢慢地由autorelease,再演化成由系統透過 Strong, Weak , Unowned 來判斷記憶體回收的時機

比較表

Strong Weak Unowned
Reference Count O
Optional O O
Non-Optional O O
var O O O
let O O

說明

  1. 只有 Strong 會增加 Reference Count
  2. Strong / Weak 可以是 nil, 但是 unowned 不能為nil
  3. Weak 不能用 let 宣告,因為在引用的過程中避免循環,所以只能讀 不能寫

來個常見網路使用的範例


//呼叫API  完成後用completion(trailing closure) 回傳後繼續

class API {
    var completion: ((Data?, Error?) -> Void) = { (_,_) in }
}  

//給一個 ViewController 長成醬子

class VC: UIViewController {  
    let api = API()

    func loading(_ show:Bool){}

    func loadAPI() {
        loading(true)
        api.completion = { (data, error) in
            loading(false)  //這裡會出錯
        }
    }
}

這是個常見的例子 我們在讀取API的前和後,想要秀出轉轉轉的 UIActivityIndicator 讓使用者知道現在正在讀取API,在API讀完之後,將 UIActivityIndicator 設定會隱藏。

在閉包的程式區塊裡,想要使用外部的變數,或是使用外部的函數,Xcode 會提示你加一個 self.

      func loadAPI() {
        loading(true)
        api.completion = { (data, error) in
            self.loading(false)  //這裡會出錯
        }
      }

在前面,用以得知,這個是由上一層來的。又因為 Closure 是 Reference Type, 在閉包裡的參數變化,外部的變數值也會跟著變化,因此, 這裡的 Xcode 其實是會報錯的,閉包會把 self.loading 咬住而讓 VC 無法被釋放。

正確的做法是在閉包裡,對於 self 的引用,由 strong 改為 weak / optional type

      func loadAPI() {
        loading(true)
        api.completion = { [weak self] (data, error) in
            self?.loading(false)  
        }
      }

如此一來,當外部的 self 不存在時 閉包裡的 loading 也不會強迫使用 外部的 self

那?有沒有其他的解法?

      func loadAPI() {
        loading(true)
        api.completion = { [unowned self] (data, error) in
            self.loading(false)  
        }
      }

使用 unowned 時, self 後不用加 ?

例外?

這是一個 animate 的 closure, 因為要對 view 做透明度的變化,在閉包裡的 view ,要在前面加一個 self, 那麼為什麼不用在閉包裡宣告 weak 或是 unowned ? 那是因為 view 的生命週期,只執行一次,不會留存在記憶體裡面重覆使用,因此 沒有 refernece count,自然也沒有 retain cycle 的問題


UIView.animate(withDuration: 1.0) {
    self.view.alpha = 0.5
}

轉載請註明來源,歡迎對文章中的引用來源進行考證,歡迎指出任何有錯誤或不夠清晰的表達。可以郵件至 [email protected]