Swift的ARC和內存泄漏

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將自動爲您釋放對象。json

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() 方法。api


內存泄漏

在Swift中,內存泄漏存在與循環引用中。當兩個對象彼此保持強引用時,會發生循環引用。bash

這一般發生在逃逸閉包中。微信

在使用逃逸閉包寫回調時,任何使用任何當前類的方法或者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的強引用。 異步

即便沒有其餘對這些對象的引用,ARC也永遠沒法清理FeedViewController或NetworkHelper。這將致使兩個對象存在,直到用戶關閉應用程序。若是從新建立FeedViewController,則可能再次發生循環引用,從而使問題更加嚴重。

弱引用和無主引用

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

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具備很好的功能,能夠檢查應用程序運行時存在的內存使用狀況,引用和對象實例。

Xcode Memory Graph暫停您的應用程序執行並顯示當前存在的全部對象。您還能夠選擇對象的實例,並查看哪些對象包含對它的引用。

當內存泄漏確實發生時,Xcode甚至常常用紫色的解釋點突出顯示有問題的類。添加一個簡單的保留週期並屢次執行該操做會產生以下所示的內存圖。

單擊其中一個實例進一步顯示閉包持有對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人了,想要加羣的小夥伴們能夠掃碼加這個微信,備註:「加羣+暱稱」,拉你進羣,謝謝了

相關文章
相關標籤/搜索