Swift管理內(nèi)存方式和OC極其相似,都是采用自動引用計數(shù) ARC
機(jī)制來跟蹤和管理。
ACR
會在實例不再使用的情況下自己釋放其占用的內(nèi)存空間吊输,所以通常情況下不需要我們自己來手動釋放环鲤。
引用計數(shù)應(yīng)用于類的實例
(注意:結(jié)構(gòu)體
和枚舉
是值類型So結(jié)構(gòu)體和枚舉不是通用引用計數(shù)來管理內(nèi)存的等脂。)
本章結(jié)合了Swift 官方文檔裁奇。挑一些主要點(diǎn)進(jìn)行描述Swift的引用計數(shù)機(jī)制
章節(jié)如下:
- 自動引用計數(shù)工作機(jī)制
- 自動引用計數(shù)實踐
- 類實例之間的循環(huán)強(qiáng)引用
- 解決實例之間的循環(huán)強(qiáng)引用
- 閉包(即OC-Block)引起的循環(huán)強(qiáng)引用
- 解決閉包引起的循環(huán)強(qiáng)引用
1.自動引用計數(shù)工作機(jī)制
簡單說就是每當(dāng)我們創(chuàng)建一個實例的時候ARC
都會開辟一塊內(nèi)存空間.供這個實例使用稚补,內(nèi)存中會包含這個實例及這個實例的類型信息。無論這個實例是被賦值為屬性,變量或常量
框喳。直到我們不再使用這個實例,ARC
會自動釋放此實例,并將這塊內(nèi)存空間挪它使用厦坛。其機(jī)制和OC
一樣五垮,若訪問被釋放的實例則Crash。只要這個實例仍存在,強(qiáng)引用就伴隨其一直存在杜秸,就可訪問放仗。
2.自動引用計數(shù)實踐
若上面這段富有理論性的話大家不太明白,請注意看這個栗子
class Person {
let name: String
init(name: String) {//init 構(gòu)造函數(shù)撬碟。"人"這個實例對象,具有一個自己的屬性"名字"诞挨,人被初始化了,其名字也同時被初始化。
self.name = name
print("\(name) is being initialized")
}
deinit {//人掛了呢蛤,其屬性肯定也掛了(”deinit析構(gòu)函數(shù)“ 等同OC dealloc )
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
/*我們有三個為Person類型的變量. 注意問號"?". 代表可選類型惶傻,也就是,可為空其障。
所以银室,這時候還沒有強(qiáng)引用,這三個變量的初始值都為nil.還沒有被創(chuàng)建.*/
/*
接下來我們創(chuàng)建Person類實例對象,給其中一個變量賦值.注意:這時候reference1到Person類實例之間建立強(qiáng)引用蜈敢,所以ARC保證Person一直存在內(nèi)存中辜荠。可以看到打印抓狭。
*/
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized
/*
此時,我們將Person也賦值給其它兩個變量,這時候Person強(qiáng)引用加二
*/
reference2 = reference1
reference3 = reference1
/*
此時我們再將兩個變量賦值為nil.則Person就少去兩個強(qiáng)引用伯病。注意:此時Person還是存在的哈,因為還存在一個強(qiáng)引用,還有一個變量是它否过。
*/
reference1 = nil
reference2 = nil
/*
好了午笛,我們可手動銷毀最后一個強(qiáng)引用。這時候ARC會釋放掉Person,可看打印叠纹。
*/
reference3 = nil
// 打印 “John Appleseed is being deinitialized
3.類實例之間的循環(huán)強(qiáng)引用
栗子
有兩個類季研,Person
Apartment
.
Person
具有實例屬性為apartment
,apartment
是可選的,所以初始值為nil
,所以這時候Preson
并不會產(chǎn)生強(qiáng)引用加一。這塊兒注意誉察,這是一會演示產(chǎn)生循環(huán)強(qiáng)引用的關(guān)鍵.
Apartment
.同Person
一樣也有個初始化為nil
的實例屬性Person
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
現(xiàn)在我在另一個類中寫兩個實例屬性与涡,分別為Person
和Apartment
的初始值為nil
的可選屬性john,unit4
.
var john: Person?
var unit4A: Apartment?
現(xiàn)在創(chuàng)建特定的Person
和Apartment
實例,并將其賦值給john和unit4A變量
注意了:這時候john
和Person
之間建立了強(qiáng)引用.unit4A
和Apartment
之間也建立了強(qiáng)引用.
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
現(xiàn)在我將unit4A
賦值給John
的apartment
持偏,將john
賦值給unit4A
的tenant
注意了:關(guān)鍵就在這兒了驼卖,這時候john
和 unit4A
的內(nèi)部實例屬性是有強(qiáng)引用的.所以在下面我將john
和unit4A
,手動制空,其會被ARC釋放嗎?析構(gòu)函數(shù)deinit
會被調(diào)用嗎? 答案是肯定不會鸿秆,這就造成了類實例之間的循環(huán)強(qiáng)引用.
為什么?雖然我將john
和john
手動制空,釋放了一個強(qiáng)引用酌畜,但是其內(nèi)部是不是還有個實力屬性擁有強(qiáng)引用哇?卿叽。這就造成了內(nèi)存泄漏,不得了了桥胞。
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
unit4A = nil
那怎么解決呢? 請往下看
4.解決實例之間的循環(huán)強(qiáng)引用
Swift 提供了兩種方法來解決使用類的屬性時,所遇到的循環(huán)強(qiáng)引用問題.
弱引用weak reference, 無主引用unowned reference
當(dāng)其他的實例有更短的生命周期使用弱引用考婴,反之使用無主引用.
- ****弱引用****
關(guān)鍵字weak
當(dāng)其他的實例有更短的生命周期時贩虾,使用弱引用,也就是說沥阱,當(dāng)其他實例析構(gòu)在先時缎罢。
修飾可為nil
的,可選類型變量。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 打印 “John Appleseed is being deinitialized
unit4A = nil
// 打印 “Apartment 4A is being deinitialized
看懂沒? 沒錯,就是
weak
關(guān)鍵字修飾Apartment
類的實例屬性Person
ARC
會在實例的引用被銷毀的時候自動將其被weak
修飾的實例屬性設(shè)置為nil
策精,所以當(dāng)john
掛了,它也就徹底掛了,弱引用不會對其引用的實例進(jìn)行強(qiáng)引用!注意 當(dāng)
ARC
設(shè)置弱引用沒nil
時崇棠,它的屬性觀察器是不會被觸發(fā)的.PS:
Apartment
和Person
的例子很好的演示了咽袜,兩個類都擁有一個可為nil的可選屬性。并且在另一個類中易茬,這兩個類的實例屬性相互持有酬蹋。這就產(chǎn)生的潛在的循環(huán)強(qiáng)引用及老,這時候我們用弱引用weak
再適合不過了.
- ****無主引用****
在屬性或變量前面加一個關(guān)鍵字unowned
表示這是一個無主引用。
無主引用和弱引用一樣不會牢牢保持一個實例的引用范抓。
不同就是ARC不會在實例銷毀后將其設(shè)置為nil骄恶。因為其是非可選 的類型的變量,所以不允許被設(shè)置為nil
。
直接看栗子
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized
兩個類,Customer
信用卡客戶,和CreditCard
信用卡
信用卡客戶:有個名字是肯定的let name: String
,信用卡不一定有var card: CreditCard?
信用卡:卡號和卡持有者匕垫,是肯定有的,所以都用let
修飾.注意正是因為這個用無主引用關(guān)鍵字unowned
修飾的實例對象Customer
卡片持有者僧鲁,所以之后相互引用才不會導(dǎo)致循環(huán)強(qiáng)引用.然后我們必須通過卡號和持卡者創(chuàng)建信用卡.
看圖示
Customer
,和CreditCard
,Customer
一個屬性值可為nil偶惠,一個屬性值不可為nil春寿,然后在里一個類中,這兩個類相互引用忽孽,產(chǎn)生強(qiáng)引用绑改。這種情況無主引用最合適不過了。
- ****無主引用以及隱式解析可選屬性****
栗子
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
}
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa
感嘆號(City!)的方式兄一,將Country的capitalCity屬性聲明為隱式解析可選類型的屬性厘线。這意味著像其他可選類型一樣,capitalCity屬性的默認(rèn)值為nil出革,但是不需要展開它的值就能訪問它造壮。
capitalCity屬性在初始化完成后,能像非可選值一樣使用和存取骂束,同時還避免了循環(huán)強(qiáng)引用耳璧。
5. 閉包引起的循環(huán)引用
閉包中會如何產(chǎn)生循環(huán)強(qiáng)引用?
閉包中引用了self
,比如self.someProperty
,self.someMethod
為什么?
這是因為閉包和類相似都是屬于引用類型
。當(dāng)我們把閉包賦值給類的某個屬性展箱,其實是把這個引用賦值給這個屬性楞抡。和上面類實例之前的循環(huán)強(qiáng)引用一樣,會產(chǎn)生兩個強(qiáng)引用一直存在析藕。是不是不太好理解?來我們看栗子
在上圖例子相信大家就會明白账胧,什么是閉包引起的循環(huán)強(qiáng)引用,以及怎樣解決閉包引起的循環(huán)強(qiáng)引用先紫。
有的同學(xué)可能不太理解治泥,我靠,這是什么寫法遮精?這是Swift的捕獲列表居夹。
捕獲列表:
捕獲列表中的每一項都由一對元素組成败潦,一個元素是weak或unowned關(guān)鍵字,另一個元素是類實例的引用(例如self)或初始化過的變量(如delegate = self.delegate!)
****總結(jié):****
ARC自動幫我們管理內(nèi)存准脂,也是采用垃圾回收機(jī)制劫扒。
切記:
兩個類的實例相互保持對方的強(qiáng)引用時會產(chǎn)生循環(huán)強(qiáng)引用。閉包會產(chǎn)生循環(huán)強(qiáng)引用,尤其Rx
,RAC
的回調(diào)實現(xiàn)中很敏感狸膏。
開發(fā)中通常我們不會手動將實例對象制空沟饥。若制空,再訪問那么湾戳,你懂得贤旷。
主要說下循環(huán)強(qiáng)引用:
循環(huán)強(qiáng)引用實質(zhì)上說白了就是,這個類最終沒有釋放最終沒走deinit方法砾脑。為什么幼驶?有可能是你的類與實力之間產(chǎn)生的循環(huán)強(qiáng)引用,有可能是你的閉包引起的循環(huán)強(qiáng)引用韧衣。
怎么解決盅藻?大家可以參考上面的例子。在實際開發(fā)中呢汹族,我們最常見的就是閉包引起的循環(huán)引用萧求。這里總結(jié)下解決辦法
//捕獲列表:
object.block = { [weak self] in // 弱引用
self.xxx
}
object.block = { [unowned self] in //無主引用
self.xxx
}
//若引用修飾self:
weak var ws = self
object.block = { [unowned self] in
ws.xxx
}
weak和unowned區(qū)別:
unowned 更像OC的 unsafe_unretained ,而 weak 就是以前的 weak 顶瞒。
unowned設(shè)置以后即使它原來引用的內(nèi)容已經(jīng)被釋放了夸政,它仍然會保持對被已經(jīng)釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值榴徐,也不會被指向 nil 守问。如果你嘗試調(diào)用這個引用的方法或者訪問成員屬性的話,程序就會崩潰坑资。
weak 在引用的內(nèi)容被釋放后耗帕,標(biāo)記為 weak 的成員將會自動地變成 nil (所以被標(biāo)記為 weak 的變量一定需要是 Optional 值)。
關(guān)于兩者使用的選擇袱贮,存在被釋放的可能仿便,那就選擇用 weak 。開發(fā)中用weak 會多一些.
題外話:最近忙了一陣兒攒巍,回頭續(xù)寫嗽仪,寫了一半的文章,發(fā)現(xiàn)太不干了柒莉。所以以后的文章會盡量寫一寫干的東西闻坚。寫這么大一篇文章,我覺得直接看總結(jié)這部分就好了兢孝。??