Swift中如何避免循環(huán)引用的方法
內(nèi)存管理中經(jīng)常會(huì)遇到的一個(gè)問(wèn)題便是循環(huán)引用。首先,我們來(lái)了解一下iOS是如何進(jìn)行內(nèi)存管理的。
和OC一樣,swift也是使用自動(dòng)引用計(jì)數(shù)ARC(Auto Reference Counteting)來(lái)自動(dòng)管理內(nèi)存的,所以我們不需要過(guò)多考慮內(nèi)存管理.當(dāng)某個(gè)類(lèi)實(shí)例不需要用到的時(shí)候,ARC會(huì)自動(dòng)釋放其占用的內(nèi)存.
ARC
ARC(Automatic Reference Counting) 是蘋(píng)果的自動(dòng)內(nèi)存管理機(jī)制。正如其名:自動(dòng)引用計(jì)數(shù),根據(jù)引用計(jì)數(shù)來(lái)決定內(nèi)存塊是否應(yīng)該被釋放。
當(dāng)一個(gè)對(duì)象被創(chuàng)建的時(shí)候,它的引用計(jì)數(shù)為1。在它的生命周期內(nèi),其引用計(jì)數(shù)可以增加或減少。當(dāng)它的引用計(jì)數(shù)減為0的時(shí)候,其所占用內(nèi)存便會(huì)被釋放。其生命周期如圖所示:
強(qiáng)引用和弱引用(Strong/Weak References)
定義一個(gè)變量的時(shí)候可以聲明其strong和weak屬性,默認(rèn)是strong類(lèi)型。
struct Example { var strongView = UIView() weak var weakView = UIView() }
強(qiáng)引用和弱引用有什么不同呢?
強(qiáng)引用會(huì)使變量的引用計(jì)數(shù)加1。如果一個(gè)對(duì)象的引用計(jì)數(shù)為2,當(dāng)它再次被強(qiáng)引用的時(shí)候,它的引用計(jì)數(shù)會(huì)變?yōu)?。
弱引用不會(huì)增加引用計(jì)數(shù)。如果一個(gè)對(duì)象的引用計(jì)數(shù)為2,當(dāng)它再次被弱引用的時(shí)候,它的引用計(jì)數(shù)仍為2。
強(qiáng)引用的對(duì)象能保證其被調(diào)用的時(shí)候仍在內(nèi)存中,而弱引用不行。
循環(huán)引用和內(nèi)存泄漏
當(dāng)A引用B中的成員變量,而B(niǎo)又對(duì)A中的成員變量有引用的時(shí)候就會(huì)發(fā)生循環(huán)引用。
比如:
class Book { private var pages = [Page]() func add(_ page : Page) { pages.append(page) } } class Page { private var book : Book required init(book : Book) { self.book = book } } let book = Book() let page = Page(book: book) book.add(page)
此時(shí),book對(duì)page有強(qiáng)引用,同時(shí)page對(duì)book也有強(qiáng)引用。這個(gè)時(shí)候便有循環(huán)引用,會(huì)導(dǎo)致內(nèi)存泄漏。
對(duì)于這種兩個(gè)變量的相互強(qiáng)引用導(dǎo)致的內(nèi)存泄漏該如何解決呢?
Structs 和 Classes
正確的使用struct 和 class能避免循環(huán)引用的發(fā)生。
struct 和 class 都有成員變量,函數(shù)和協(xié)議。那么,它們之間有什么區(qū)別呢?
struct 是 值類(lèi)型。
class 是 引用類(lèi)型。
當(dāng)引用或者傳遞 值類(lèi)型 變量的時(shí)候,它會(huì)在內(nèi)存中重新分配地址,copy內(nèi)容到新的地址中。
struct Element { var name : String var number : Int } var firstElement = Element(name: "A", number: 1) var secondElement = firstElement secondElement.name = "B" secondElement.number = 2 print(firstElement) print(secondElement)
輸出的結(jié)果為:
Element(name: “A”, number: 1) Element(name: “B”, number: 2)
當(dāng)引用或者傳遞 引用類(lèi)型 變量的時(shí)候,新的變量指針指向的仍是原先的內(nèi)存地址。此時(shí)原先的變量值改變的話(huà),也會(huì)導(dǎo)致新變量值的變化。
比如:
class Element { var name : String var number : Int required init(name : String, number : Int) { self.name = name self.number = number } } extension Element : CustomStringConvertible { var description : String { return "Element(name: \(name), number: \(number))" } } var firstElement = Element(name: "A", number: 1) var secondElement = firstElement secondElement.name = "B" secondElement.number = 2 print(firstElement) print(secondElement)
此時(shí)的輸出結(jié)果為:
Element(name: B, number: 2) Element(name: B, number: 2)
我們?yōu)槭裁丛诖擞懻撝殿?lèi)型和引用類(lèi)型呢?
回到之前book和pages的例子。我們用struct代替class:
struct Book { private var pages = [Page]() mutating func add(_ page : Page) { pages.append(page) } } struct Page { private var book : Book init(book : Book) { self.book = book } } var book = Book() let page = Page(book: book) book.add(page)
此時(shí),便不會(huì)發(fā)生循環(huán)引用的情況。
如果仍想使用class的話(huà),可以使用weak來(lái)避免循環(huán)引用:
class Book { private var pages = [Page]() func add(_ page : Page) { pages.append(page) } } class Page { private weak var book : Book? required init(book : Book) { self.book = book } } let book = Book() let page = Page(book: book) book.add(page)
Protocols
Protocols在swift中使用的很廣泛。class,struct 和 enum 都可以使用Protocol。但是如果使用不當(dāng)?shù)脑?huà),同樣會(huì)引起循環(huán)引用。
比如:
protocol ListViewControllerDelegate { func configure(with list : [Any]) } class ListViewController : UIViewController { var delegate : ListViewControllerDelegate? override func viewDidLoad() { super.viewDidLoad() } }
ListViewController 中的delegate變量是strong類(lèi)型的,可以引用任何實(shí)現(xiàn)它protocol的變量。假如實(shí)現(xiàn)其protocol的變量對(duì)該 view controller 同樣有強(qiáng)引用的話(huà)會(huì)怎么樣? 聲明delegate為weak可能會(huì)避免這種情況,但是這樣的話(huà)會(huì)引起編譯錯(cuò)誤,因?yàn)閟tructs和enums不能引用weak變量。
該如何解決呢?當(dāng)聲明protocol的時(shí)候,我們可以指定只有class類(lèi)型的變量可以代理它,這樣的話(huà)就可以使用weak來(lái)修飾了。
protocol ListViewControllerDelegate : class { func configure(with list : [Any]) } class ListViewController : UIViewController { weak var delegate : ListViewControllerDelegate? override func viewDidLoad() { super.viewDidLoad() } }
Closures
Closures 導(dǎo)致循環(huán)引用的原因是:Closures對(duì)使用它們的對(duì)象有一個(gè)強(qiáng)引用。
比如:
class Example { private var counter = 0 private var closure : (() -> ()) = { } init() { closure = { self.counter += 1 print(self.counter) } } func foo() { closure() } }
此時(shí),對(duì)象對(duì)closure有一個(gè)強(qiáng)引用,同時(shí)在closure的代碼塊中又對(duì)該對(duì)象本身有一個(gè)強(qiáng)引用。這樣就引起了循環(huán)引用的發(fā)生。
這種情況,可以有兩種方法來(lái)解決這個(gè)問(wèn)題。
1.使用[unowned self]:
class Example { private var counter = 1 private var closure : (() -> ()) = { } init() { closure = { [unowned self] in self.counter += 1 print(self.counter) } } func foo() { closure() } }
使用[unowned self] 的時(shí)候需要注意的一點(diǎn)是:調(diào)用closure的時(shí)候如果對(duì)象已經(jīng)被釋放的話(huà),會(huì)出現(xiàn)crash。
2.使用[weak self]:
class Example { private var counter = 1 private var closure : (() -> ()) = { } init() { closure = { [weak self] in self?.counter += 1 print(self?.counter ?? "") } } func foo() { closure() } }
[weak self] 和[unowned self] 的區(qū)別是 [weak self]處理的時(shí)候是一個(gè)可選類(lèi)型。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Swift開(kāi)發(fā)中switch語(yǔ)句值綁定模式
本文給大家分享Swift開(kāi)發(fā)中switch語(yǔ)句值綁定模式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下2016-12-12swift4.0實(shí)現(xiàn)視頻播放、屏幕旋轉(zhuǎn)、倍速播放、手勢(shì)調(diào)節(jié)及鎖屏面板等功能實(shí)例
這篇文章主要給大家介紹了關(guān)于swift4.0實(shí)現(xiàn)視頻播放、屏幕旋轉(zhuǎn)、倍速播放、手勢(shì)調(diào)節(jié)及鎖屏面板等功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01Swift算法實(shí)現(xiàn)字符串轉(zhuǎn)數(shù)字的方法示例
最近學(xué)完了swift想著實(shí)踐下,就通過(guò)一些簡(jiǎn)單的算法進(jìn)行學(xué)習(xí)研究,下面這篇文章主要介紹了Swift算法實(shí)現(xiàn)字符串轉(zhuǎn)數(shù)字的方法,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-03-03Swift語(yǔ)言實(shí)現(xiàn)地圖坐標(biāo)彈跳動(dòng)畫(huà)
這篇文章主要介紹了用Swift語(yǔ)言實(shí)現(xiàn)地圖坐標(biāo)彈跳動(dòng)畫(huà)的方法主要應(yīng)用iOS7來(lái)實(shí)現(xiàn)此功能,需要的朋友可以參考下2015-07-07Swift中類(lèi)與結(jié)構(gòu)的初始化示例解析
這篇文章主要為大家介紹了Swift中類(lèi)與結(jié)構(gòu)的初始化解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03Swift3.0剪切板代碼拷貝及跨應(yīng)用粘貼實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Swift3.0剪切板代碼拷貝及跨應(yīng)用粘貼的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03如何利用SwiftUI實(shí)現(xiàn)可縮放的圖片預(yù)覽器
這篇文章主要給大家介紹了關(guān)于如何利用SwiftUI實(shí)現(xiàn)可縮放圖片預(yù)覽器的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SwiftUI具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09