Swift的ARC和記憶體洩漏

敲鐘人Quasimodo發表於2019-03-04

ARC

Swift引入了一項稱為自動引用計數(ARC)的強大功能,可以處理應用程式的大部分記憶體管理。然而,初學者程式設計師通常不知道它是如何運作的。

ARC負責分配和釋放物件使用的記憶體。為了使其自動化,ARC計算對您建立的變數的引用。

class Object {
    var name: String
}

let object = Object()       //ARC object reference count: 1
var reference2 = object     //ARC object reference count: 2
var reference3 = reference1 //ARC object reference count: 3
var reference4 = object     //ARC object reference count: 4
複製程式碼

刪除物件的引用後,ARC將自動為您釋放物件。

let object = null        //ARC object reference count: 3
var reference2 = null    //ARC object reference count: 2
var reference3 = null    //ARC object reference count: 1
var reference4 = null    //ARC object reference count: 0
複製程式碼

當ARC釋放記憶體時,將呼叫物件的deinit() 方法。


記憶體洩漏

在Swift中,記憶體洩漏存在與迴圈引用中。當兩個物件彼此保持強引用時,會發生迴圈引用。

這通常發生在逃逸閉包中。

在使用逃逸閉包寫回撥時,任何使用任何當前類的方法或者self都將建立對該例項的強引用,從而造成迴圈引用,因為閉包將保持對類的強引用,並且類將保留對閉包所在類的強引用。

下面的示例顯示了使用逃逸閉包時的常見的迴圈引用。

class NetworkHelper {
    func getFeed(completion: @escaping ([FeedItem]) -> Void) {
        Alamofire.request(…).responseJSON { (response) in
            if let value = response.result.value {
                if let json = JSON(value)[Constants.items].array {
                    completion(json.flatMap(FeedItem.init))
                }
            }
        }
    }
}
class FeedViewController {
    var tableView: UITableViewController
    var feedItems: [FeedItem]
    var networkHelper: NetworkHelper
    override func viewDidLoad() {
        ...
        networkHelper.getFeed() { items in
            self.feedItems = items
            self.tableView.reloadData()
        }
    }
}
複製程式碼

在上面的示例中,FeedViewController通過變數networkHelper儲存對NetworkHelper的強引用。然後,對FeedViewController的引用作為閉包傳遞給networkHelper,從而建立從networkHelper到FeedViewController的強引用。

Swift的ARC和記憶體洩漏
即使沒有其他對這些物件的引用,ARC也永遠無法清理FeedViewController或NetworkHelper。這將導致兩個物件存在,直到使用者關閉應用程式。如果重新建立FeedViewController,則可能再次發生迴圈引用,從而使問題更加嚴重。

弱引用和無主引用

要中斷迴圈引用,必須從傳遞給networkHelper的閉包中刪除對FeedViewController的強引用。這是通過使用弱或無主的自我來完成的。

weak關鍵字通過不遞增ARC的引用計數來建立對變數的弱引用。但是,由於它不是強引用,因此無法保證在執行閉包時物件將存在。因此,只要您使用weak關鍵字,該變數就是可選的。

unowned關鍵字也不會增加ARC的引用計數,但它也不是可選的。但是由於它沒有對變數的強引用,它可能不存在並且可能完全指向其他東西。除非兩個物件始終像計算機及其處理器一樣存在,否則不應使用unowned

要修復上面的示例,您只需指定self是弱引用,保留週期將被破壞。

class FeedViewController {
var tableView: UITableViewController
    var feedItems: [FeedItem]
    var networkHelper: NetworkHelper
override func viewDidLoad() {
        ...
        networkHelper.getFeed() { [weak self] items in
            self?.feedItems = items
            self?.tableView.reloadData()
        }
    }
}
複製程式碼

除錯記憶體管理

XCode具有很好的功能,可以檢查應用程式執行時存在的記憶體使用情況,引用和物件例項。

Swift的ARC和記憶體洩漏
Xcode Memory Graph暫停您的應用程式執行並顯示當前存在的所有物件。您還可以選擇物件的例項,並檢視哪些物件包含對它的引用。

當記憶體洩漏確實發生時,Xcode甚至經常用紫色的解釋點突出顯示有問題的類。新增一個簡單的保留週期並多次執行該操作會產生如下所示的記憶體圖。

Swift的ARC和記憶體洩漏
單擊其中一個例項進一步顯示閉包持有對DetailsViewController的引用,我在其中建立了以下迴圈引用。

let retainCycle = RetainCycle()
override func viewDidLoad() {
    retainCycle.keepMe { 
        self.view.backgroundColor = .white 
    }
}
class RetainCycle {
    func keepMe(closure: @escaping () -> Void) {
        URLSession.shared.dataTask(...) { (data, _, _) in 
            closure()
        }
    }
}
複製程式碼

因此,下次建立閉包時,無論是NotificationCenter上的觀察者,網路呼叫還是其他非同步任務,都要記得在記憶體洩漏之前之前檢查一下。 PS:

最近加了一些iOS開發相關的QQ群和微信群,但是感覺都比較水,裡面對於技術的討論比較少,所以自己建了一個iOS開發進階討論群,歡迎對技術有熱情的同學掃碼加入,加入以後你可以得到:

  1. 技術方案的討論,會有在大廠工作的高階開發工程師儘可能抽出時間給大家解答問題

  2. 每週定期會寫一些文章,並且轉發到群裡,大家一起討論,也鼓勵加入的同學積極得寫技術文章,提升自己的技術

  3. 如果有想進大廠的同學,裡面的高階開發工程師也可以給大家內推,並且針對性得給出一些面試建議

群已經滿100人了,想要加群的小夥伴們可以掃碼加這個微信,備註:“加群+暱稱”,拉你進群,謝謝了

Swift的ARC和記憶體洩漏

相關文章