版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.07.31 |
前言
我是swift2.0的時候開始接觸的,記得那時候還不是很穩定,公司的項目也都是用oc做的,并不對swift很重視,我自己學了一段時間,到現在swift3.0+已經出來了,自己平時也不寫,忘記的也差不多了,正好項目這段時間已經上線了,不是很忙,我就可以每天總結一點了,希望對自己對大家有所幫助。在總結的時候我會對比oc進行說明,有代碼的我會給出相關比對代碼。
1. swift簡單總結(一)—— 數據簡單值和類型轉換
2. swift簡單總結(二)—— 簡單值和控制流
3. swift簡單總結(三)—— 循環控制和函數
4. swift簡單總結(四)—— 函數和類
5. swift簡單總結(五)—— 枚舉和結構體
6. swift簡單總結(六)—— 協議擴展與泛型
7. swift簡單總結(七)—— 數據類型
8. swift簡單總結(八)—— 別名、布爾值與元組
9. swift簡單總結(九)—— 可選值和斷言
10. swift簡單總結(十)—— 運算符
11. swift簡單總結(十一)—— 字符串和字符
12. swift簡單總結(十二)—— 集合類型之數組
13. swift簡單總結(十三)—— 集合類型之字典
14. swift簡單總結(十四)—— 控制流
15. swift簡單總結(十五)—— 控制轉移語句
16. swift簡單總結(十六)—— 函數
17. swift簡單總結(十七)—— 閉包(Closures)
18. swift簡單總結(十八)—— 枚舉
19. swift簡單總結(十九)—— 類和結構體
20. swift簡單總結(二十)—— 屬性
21. swift簡單總結(二十一)—— 方法
22. swift簡單總結(二十二)—— 下標腳本
23. swift簡單總結(二十三)—— 繼承
24. swift簡單總結(二十四)—— 構造過程
25. swift簡單總結(二十五)—— 構造過程
26. swift簡單總結(二十六)—— 析構過程
自動引用計數
和OC
一樣,swift
也有引用自動引用計數,用來管理內存。引用計數僅僅應用于類的實例。結構體和枚舉類型都是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。
主要從下面幾個方向講述。
- 自動引用計數的工作機制
- 自動引用計數實踐
- 類實例之間的循環強引用
- 解決實例之間循環強引用
- 閉包引起的循環強引用
- 解決閉包引起的循環強引用
自動引用計數的工作機制
和OC
一樣,swift
中為了確保使用中的實例不被銷毀,ARC會跟蹤和計算每一個實例正在被多少屬性、常量和變量引用,只要引用計數器不為1,ARC就不會銷毀這個實例。無論是將實例賦值給常量、變量或者屬性,都會對此實例創建強引用,將這個實例牢牢抓住
。
自動引用計數實踐
先看一個代碼。
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
在Person
類里面定義了構造函數和析構函數。下面定義三個類型為Person
的變量,用來按照代碼片段中順序,為新的Person
實例建立引用,如下所示:
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
這里要注意,這些變量被定義為可選類型Person?
不是Person
,它們的值會被自動初始化為nil
,目前還不會引用到Person
類的實例。
下面我們建立實例。
class JJPracticeVC: UIViewController {
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
reference1 = Person(name: "John")
reference2 = Person(name: "Rose")
reference3 = Person(name: "Nick")
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
下面我們看一下
John is being initialized
Rose is being initialized
Nick is being initialized
可見析構函數沒有調用,也就是三個對象都沒有銷毀,想要解除強引用,只需要給相關對象賦值為nil
即可。
class JJPracticeVC: UIViewController {
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
reference1 = Person(name: "John")
reference2 = Person(name: "Rose")
reference3 = Person(name: "Nick")
reference2 = nil
reference3 = nil
}
}
下面看輸出結果
John is being initialized
Rose is being initialized
Nick is being initialized
Rose is being deinitialized
Nick is being deinitialized
可見,reference2
和reference3
被銷毀了。
類實例之間的循環強引用
循環引用的概念與OC
中是一樣的,如果鏈各個類實例之間互相保持對方的強引用,并讓對方不被銷毀,這就是所謂的循環強引用。在swift
中解除強引用的方法就是通過定義類之間的關系為弱引用或者無主引用,以此替代強引用,從而解決循環強引用的問題。
下面就給出一個強引用的例子。
class JJPracticeVC: UIViewController {
var john : Person?
var number : Apartment?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Person(name: "John")
number = Apartment(number: 73)
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
var apartment : Apartment?
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number
}
var tenant : Person?
deinit {
print("Apartment \(number) is being deinitialized")
}
}
下面看輸出結果
John is being initialized
看一下效果圖。
他們的析構函數都沒有被調用,也就是說他們有了強引用,無法釋放對象。
解決實例之間的循環強引用
swift
提供了兩種方法用來解決你在使用類的屬性時所遇到的循環強引用問題:
- 弱引用
weak reference
- 無主引用
unowned reference
弱引用和無主引用允許循環引用中的一個實例引用另外一個實例而不保持強引用,這樣能夠互相引用而不產生強引用。
對于生命周期中變為nil
的實例使用弱引用,相反的,對于初始化賦值后再也不會被賦值為nil
的實例,使用無主引用。
1. 弱引用
弱引用不會牢牢保持住引用的實例,并且不會阻止ARC
銷毀被引用的實例,這種行為阻止了引用變為循環強引用,聲明屬性或者變量時,在前面加上weak
關鍵字表明這是一個弱引用。
弱引用還需要注意下面幾點
- 在實例的生命周期中,如果某些時候引用沒有值,那么弱引用可以阻止循環強引用,如果引用總是有值,則可以使用無主引用。在上面
Apartment
的例子中,一個公寓的生命周期中,有時候是沒有居民的,所以適合使用弱引用來解決循環強引用。 - 弱引用必須生命為變量,表明其值能在運行時被修改,弱引用不能被聲明為常量。
- 因為弱引用可以沒有值,你必須將每一個弱引用聲明為可選類型。
下面看一下例子。
class JJPracticeVC: UIViewController {
var john : Person?
var number : Apartment?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = nil
number = nil
john = Person(name: "John")
number = Apartment(number: 73)
john!.apartment = number
number?.tenant = john
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
var apartment : Apartment?
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number
}
weak var tenant : Person?
deinit {
print("Apartment \(number) is being deinitialized")
}
}
由于一端用weak修飾,所以現在二者效果如下:
可見二者之間不會相互強引用了,現在將他們之間的一個對象設置為nil
,二者都會打印銷毀信息,二者引用關系如下所示。
2. 無主引用
和弱引用不同的是,無主引用永遠是有值的,因此無主引用總是被定義為非可選類型,你可以在聲明常量或者變量時,在前面加上關鍵字unowned
。
還要注意:
- 如果你試圖在實例被銷毀以后,訪問該實例的無主引用,會觸發運行時錯誤,使用無主引用,你必須確保引用始終指向一個未銷毀實例。還有,如果你試圖訪問實例已經被銷毀的無主引用,程序會直接崩潰。
下面定義兩個類Customer
和CreditCard
,但是這個和前面公寓和人的關系不一樣,這個模型中,一個客戶可能有也可能沒有信用卡,但是一個信用卡一定關系一個客戶,所以Customer
類有一個可選類型card
屬性,但是CreditCard
類有一個非可選類型的customer
屬性。
下面看一下代碼。
class JJPracticeVC: UIViewController {
var john : Customer?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
}
}
class Customer {
let name : String
var card : CreditCard?
init(name : String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class CreditCard{
let number : Int
unowned let customer : Customer
init(number : Int, customer : Customer) {
self.number = number
self.customer = customer
}
deinit {
print("Card \(number) is being deinitialized")
}
}
這里,Customer
實例持有對CreditCard
實例的強引用,而CreditCard
實例持有對Customer
的無主引用,具體如下所示。
由于Customer
的無主引用,當你斷開john
變量持有的強引用時,再也沒有指向customer
實例的強引用了,如下圖所示。
class JJPracticeVC: UIViewController {
var john : Customer?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
john = nil
}
}
加一句john = nil
,我們看一下輸出結果
John is being deinitialized
Card 123456782333 is being deinitialized
可見二者都被釋放了。
3. 無主引用以及隱式解析可選屬性
上面打破循環引用給出了兩種情況“
-
Person
和Apartment
展示了兩個屬性的值都允許為nil
,并可能產生循環引用,這種情況適合采用弱引用。 -
Customer
和CreditCard
展示了一個屬性的值可以為nil,另外一個不可以為nil
的情況,并可能產生循環引用,這種情況適合采用無主引用。
還存在另外一種情況:兩個屬性都必須有值,并且初始化完成后不能為nil
,在這種情況下,需要一個類使用無主屬性,另外一個類使用隱式解析可選類型。
下面看一個簡單例子,每一個Country
國家都要有首都city
,每一個城市也必須屬于一個國家。
class Country {
let name : String
var capitalCity : City!
init(name : String, capitalName : String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name : String
unowned let country : Country
init(name : String, country : Country) {
self.name = name
self.country = country
}
}
上面的意義在于你可以通過一條語句同時創建City
和Country
的實例,而不產生循環引用,并且capitalCity
的屬性能被直接訪問,而不需要通過感嘆號來展開它的可選值。
閉包引起的循環強引用
這種情況有點類似OC
中的block
引用,循環強引用還會發生在當你將一個閉包賦值給類實例的某個屬性,并且這個閉包體中又使用了實例,這就容易引起循環引用。
swift
提供了一種優雅的方式解決這個問題,稱為閉包占用列表(closure capture list)
,下面我們看一下閉包中循環引用是如何產生的。
class HTMLElement {
let name : String
let text : String?
lazy var asHTML : () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
}
else {
return "<\(self.name)/>"
}
}
init(name : String, text : String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
注意:這里asHTML
聲明為lazy
屬性,因為只有當元素確實需要處理為HTML輸出時,才需要使用asHTML
,也就是說,在默認的閉包中可以使用self
,因為只有當初始化完成以及self
確實存在后,才能訪問lazy
屬性。
下面調用一下
var paragraph : HTMLElement? = HTMLElement(name: "p", text: "Hello,world")
下面看一下循環引用的示意圖。
這里即使將paragraph = nil
設置,也不會調用析構器。
解決閉包引起的循環引用
??在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環引用,捕獲列表定義了閉包體內捕獲一個或者多個類型的規則,跟解決兩個類實例間的循環強引用一樣,聲明每個捕獲的引用為弱引用或者無主引用,具體選擇哪個根據代碼關系確定。
注意:swift
要求只要在閉包內使用 self
的成員,就要用self.someProperty
或者self.someMethod
。
1. 定義捕獲列表
捕獲列表中的每個元素都是由weak
或者unowned
關鍵字和實例的引用成對組成,每一對都在方括號中,通過逗號分開。
捕獲列表放在閉包函數參數列表和返回類型之前。
lazy var someClosure : (Int ,Int) -> String = {
[unowned self](index : Int, stringToProcess : String) -> String in
//closure body goes here
}
如果閉包沒有指定參數列表或返回類型,則可以通過上下文判斷,那么可以捕獲列表放在閉包開始的地方,跟著關鍵字in
。
lazy var someClosure : (Int ,Int) -> String = {
[unowned self] in
//closure body goes here
}
2. 弱引用和無主引用
當閉包和捕獲的實例總是互相引用時并且總是同時銷毀時,將閉包內的捕獲定義為無主引用,相反,當捕獲引用有時可能會是nil
時,將閉包內的捕獲定義為弱引用,弱引用總是可選類型。并且當引用的實例被銷毀后,弱引用會被自動設置為nil
,這使我們可以在閉包內檢查它們是否存在。
注意如果捕獲的引用絕對不會置為nil
,應該用無主引用,而不是弱引用。
下面看一下簡單例子。
class HTMLElement {
let name : String
let text : String?
lazy var asHTML : () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
}
else {
return "<\(self.name)/>"
}
}
init(name : String, text : String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
這樣子就解除了閉包引起的循環引用。
后記
未完,待續~~~~