內存管理中經常會遇到的一個問題便是循環引用。首先,我們來了解一下iOS是如何進行內存管理的。
和OC一樣,swift也是使用自動引用計數ARC(Auto Reference Counteting)來自動管理內存的,所以我們不需要過多考慮內存管理.當某個類實例不需要用到的時候,ARC會自動釋放其占用的內存.
ARC
ARC(Automatic Reference Counting) 是蘋果的自動內存管理機制。正如其名:自動引用計數,根據引用計數來決定內存塊是否應該被釋放。
當一個對象被創建的時候,它的引用計數為1。在它的生命周期內,其引用計數可以增加或減少。當它的引用計數減為0的時候,其所占用內存便會被釋放。其生命周期如圖所示:
強引用和弱引用(Strong/Weak References)
定義一個變量的時候可以聲明其strong和weak屬性,默認是strong類型。
1
2
3
4
|
struct Example { var strongView = UIView() weak var weakView = UIView() } |
強引用和弱引用有什么不同呢?
強引用會使變量的引用計數加1。如果一個對象的引用計數為2,當它再次被強引用的時候,它的引用計數會變為3。
弱引用不會增加引用計數。如果一個對象的引用計數為2,當它再次被弱引用的時候,它的引用計數仍為2。
強引用的對象能保證其被調用的時候仍在內存中,而弱引用不行。
循環引用和內存泄漏
當A引用B中的成員變量,而B又對A中的成員變量有引用的時候就會發生循環引用。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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) |
此時,book對page有強引用,同時page對book也有強引用。這個時候便有循環引用,會導致內存泄漏。
對于這種兩個變量的相互強引用導致的內存泄漏該如何解決呢?
Structs 和 Classes
正確的使用struct 和 class能避免循環引用的發生。
struct 和 class 都有成員變量,函數和協議。那么,它們之間有什么區別呢?
struct 是 值類型。
class 是 引用類型。
當引用或者傳遞 值類型 變量的時候,它會在內存中重新分配地址,copy內容到新的地址中。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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) |
輸出的結果為:
1
2
|
Element(name: “A”, number: 1) Element(name: “B”, number: 2) |
當引用或者傳遞 引用類型 變量的時候,新的變量指針指向的仍是原先的內存地址。此時原先的變量值改變的話,也會導致新變量值的變化。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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) |
此時的輸出結果為:
1
2
|
Element(name: B, number: 2) Element(name: B, number: 2) |
我們為什么在此討論值類型和引用類型呢?
回到之前book和pages的例子。我們用struct代替class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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) |
此時,便不會發生循環引用的情況。
如果仍想使用class的話,可以使用weak來避免循環引用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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。但是如果使用不當的話,同樣會引起循環引用。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
protocol ListViewControllerDelegate { func configure(with list : [Any]) } class ListViewController : UIViewController { var delegate : ListViewControllerDelegate? override func viewDidLoad() { super.viewDidLoad() } } |
ListViewController 中的delegate變量是strong類型的,可以引用任何實現它protocol的變量。假如實現其protocol的變量對該 view controller 同樣有強引用的話會怎么樣? 聲明delegate為weak可能會避免這種情況,但是這樣的話會引起編譯錯誤,因為structs和enums不能引用weak變量。
該如何解決呢?當聲明protocol的時候,我們可以指定只有class類型的變量可以代理它,這樣的話就可以使用weak來修飾了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
protocol ListViewControllerDelegate : class { func configure(with list : [Any]) } class ListViewController : UIViewController { weak var delegate : ListViewControllerDelegate? override func viewDidLoad() { super.viewDidLoad() } } |
Closures
Closures 導致循環引用的原因是:Closures對使用它們的對象有一個強引用。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Example { private var counter = 0 private var closure : (() -> ()) = { } init() { closure = { self.counter += 1 print(self.counter) } } func foo() { closure() } } |
此時,對象對closure有一個強引用,同時在closure的代碼塊中又對該對象本身有一個強引用。這樣就引起了循環引用的發生。
這種情況,可以有兩種方法來解決這個問題。
1.使用[unowned self]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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] 的時候需要注意的一點是:調用closure的時候如果對象已經被釋放的話,會出現crash。
2.使用[weak self]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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] 的區別是 [weak self]處理的時候是一個可選類型。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.jianshu.com/p/f9bc8afd5b53