跟OC一樣,Swift也是采用基于引用計(jì)數(shù)的ARC內(nèi)存管理方案(針對(duì)堆空間)
Swift的ARC中有三種引用
- 強(qiáng)應(yīng)用(strong reference):默認(rèn)情況下,引用都是強(qiáng)引用
- 弱引用(weak reference):通過(guò)
weak
定義弱引用
- 必須是可選類型的
var
,因?yàn)閷?shí)例銷毀后,ARC會(huì)自動(dòng)將弱引用設(shè)置為nil
- ARC自動(dòng)給弱引用設(shè)置
nil
時(shí),不會(huì)觸發(fā)屬性觀察器
- 無(wú)主引用(unowned reference):通過(guò)
unowned
定義無(wú)主引用
- 不會(huì)產(chǎn)生強(qiáng)引用,實(shí)例銷毀后仍然存儲(chǔ)著實(shí)例的內(nèi)存地址(類似OC中的
unsafe_unretained
)- 試圖在實(shí)例銷毀后訪問(wèn)無(wú)主引用,會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤(野指針)
weak/unowned的使用限制
- weak、unowned只能用在類實(shí)例上面
因?yàn)橐话阒挥蓄悓?shí)例放堆空間,結(jié)構(gòu)體、枚舉一般都是不放在堆空間的
class Cat {}
protocol Actions :AnyObject {}
weak var c0: Cat?
weak var c1: AnyObject?
weak var c3: Actions?
unowned var c4: Cat?
unowned var c5: AnyObject?
unowned var c6: Actions?
上面代碼編譯都是沒(méi)問(wèn)題的。AnyObject
是可以代表任意類類型,協(xié)議Actions
也是可以的,因?yàn)樗竺媸?code>Actions :AnyObject,意思就是它的協(xié)議只能被類類型遵守。
若協(xié)議Actions
后面的冒號(hào)去掉,c3
和c6
是編譯不通過(guò)的,因?yàn)榇藚f(xié)議有可能被結(jié)構(gòu)體、枚舉遵守,而weak、unowned
只能用在類實(shí)例上面,所以編譯器提前拋出錯(cuò)誤,Swift是強(qiáng)安全語(yǔ)言。
Autoreleasepool
在Swift中,Autoreleasepool
是保留的,變成了一個(gè)全局的函數(shù):
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
使用:
class Cat {
var name: String?
init(name:String?) {
self.name = name;
}
func eat() {}
}
autoreleasepool {
let cat = Cat(name: "zhangsan")
cat.eat()
}
內(nèi)存開(kāi)銷較大的場(chǎng)景(比如數(shù)千對(duì)經(jīng)緯度數(shù)據(jù)在地圖上繪制公交路線軌跡),可以使用自動(dòng)釋放池。
循環(huán)引用(Reference Cycle)
-
weak/unowned
都能解決循環(huán)引用的問(wèn)題,unowned
要比weak
少一些性能消耗
weak
在實(shí)例銷毀的時(shí)候又設(shè)置了一遍weak
應(yīng)用為nil
,所以,性能上多了一丟丟消耗
- 在生命周期中可能會(huì)變?yōu)?code>nil的對(duì)象,使用
weak
- 初始化賦值后再也不會(huì)改變?yōu)?code>nil的對(duì)象,使用
unowned
閉包的循環(huán)引用
- 閉包表達(dá)式默認(rèn)回對(duì)用到的外層對(duì)象產(chǎn)生額外的強(qiáng)引用(對(duì)外層對(duì)象進(jìn)行了
retain
操作)
class Person {
var fn:(() -> ())?
func run() {
print("run")
}
deinit {
print("deinit")
}
}
func test() {
let p = Person()
p.fn = {
p.run()
}
}
print(1)
test()
print(2)
運(yùn)行后,打印結(jié)果只有1跟2,沒(méi)有deinit,說(shuō)明p對(duì)象一直沒(méi)有被銷毀,仔細(xì)看,問(wèn)題出在這里:
p.fn = {
p.run()
}
對(duì)象p里的fn方法強(qiáng)引用了閉包,而閉包里也強(qiáng)引用了對(duì)象p,兩者形成循環(huán)引用,對(duì)象p也就無(wú)法釋放銷毀了。
我們?cè)?code>p.fn處打斷點(diǎn),進(jìn)入?yún)R編,看看是否有強(qiáng)引用(retain):
我們注釋掉上面代碼里的
p.fn = { p.run() }
,再看匯編:可以看出,p.fn = { p.run() }
里,閉包對(duì)p
對(duì)象進(jìn)行了強(qiáng)引用也就是retain
操作,構(gòu)成了引用計(jì)數(shù)始終為1的情況,無(wú)法釋放對(duì)象。
- 在閉包表達(dá)式的捕獲列表聲明
weak
或unowned
引用,解決循環(huán)引用問(wèn)題
func test() {
let p = Person()
p.fn = {
[weak p] in
p?.run()
}
}
func test() {
let p = Person()
p.fn = {
[unowned p] in
p.run()
}
}
func test() {
let p:Person? = Person()
p?.fn = {
[weak p] in
p?.run()
}
}
func test() {
let p:Person? = Person()
p?.fn = {
[unowned p] in
p?.run()
}
}
注意:weak弱引用必須是可選類型,所以,對(duì)象
p
后面跟上?
若是unowned
修飾p
,p
后面不用跟?
,因?yàn)?code>p本身就是非可選類型,unowned
默認(rèn)情況下也就是非可選類型,是跟著p
走的
class Person {
var fn:((Int) -> ())?
func run() {
print("run")
}
deinit {
print("deinit")
}
}
func test() {
let p = Person()
p.fn = {
[weak wp = p](age) in
wp?.run()
}
}
[weak p]
是捕獲列表,(age)
是參數(shù)列表,捕獲列表一般是寫在參數(shù)列表前面的,in
后面的就是函數(shù)體。
- 如果想在定義閉包屬性的同時(shí)引用
self
,這個(gè)閉包必須是lazy
的(因?yàn)樵趯?shí)例初始化完畢之后才能引用self
)
class Person {
lazy var fn:(() -> ()) = {
self.run()
}
func run() {
print("run")
}
deinit {
print("deinit")
}
}
func test() {
let p = Person()
}
這段代碼,會(huì)打印deinit
,說(shuō)明對(duì)象p
釋放了。
為什么呢?按說(shuō)對(duì)象p
有個(gè)強(qiáng)引用fn
引用了閉包表達(dá)式,閉包表達(dá)式里也強(qiáng)應(yīng)用了self
,兩者形成循環(huán)應(yīng)用,無(wú)法釋放對(duì)象p
。
因?yàn)?code>fn是
lazy
修飾的,也就是說(shuō),在未調(diào)用p.fn
的時(shí)候是沒(méi)有值的,也就說(shuō)它后面的閉包表達(dá)不存在,自然就無(wú)法引用self
,也就不能造成循環(huán)引用。
當(dāng)?shù)谝淮握{(diào)用p.fn()
后,才會(huì)觸發(fā)fn
的初始化,創(chuàng)建閉包表達(dá)式賦值給fn
,這里就形成了循環(huán)引用。
解決循環(huán)引用:
lazy var fn:(() -> ()) = {
[weak weakSelf = self] in
weakSelf?.run()
}
lazy var fn:(() -> ()) = {
[unowned weakSelf = self] in
weakSelf.run()
}
一般用weak
,因?yàn)?code>weak比unowned
安全
- 如果
lazy
屬性是閉包調(diào)用的結(jié)果,則不用考慮循環(huán)引用的問(wèn)題(因?yàn)殚]包調(diào)用后,閉包的生命周期就結(jié)束了)
class Person {
var age: Int = 10
lazy var getAge: Int = {
self.age
}()
deinit {
print("deinit")
}
}
func test() {
let p = Person()
print(p.getAge)
}
test()
打印結(jié)果:10 deinit
內(nèi)存訪問(wèn)沖突(Conflicting Access to Memory)
- 內(nèi)存訪問(wèn)沖突會(huì)在兩個(gè)訪問(wèn)滿足下面條件時(shí)發(fā)生:
- 至少一個(gè)是寫入操作
- 它們?cè)L問(wèn)的是同一塊內(nèi)存
- 它們的訪問(wèn)時(shí)間重疊(比如在同一個(gè)函數(shù)內(nèi))
//不存在內(nèi)存訪問(wèn)沖突
func plus(_ num: inout Int) -> Int {
num + 1
}
var number = 1
number = plus(&number)
//存在內(nèi)存訪問(wèn)沖突
var step = 1
func increment(_ num: inout Int) {
//此處編譯沒(méi)問(wèn)題,但運(yùn)行報(bào)錯(cuò)
//Simultaneous accesses to 0x100008178, but modification requires exclusive access
num += step
}
increment(&step)
increment
函數(shù)內(nèi)的num += step
產(chǎn)生內(nèi)存沖突,因?yàn)?code>num雖然是形參,但外面?zhèn)鞯闹颠€是step
的內(nèi)存地址,+=
這里就造成了同一時(shí)間對(duì)同一份內(nèi)存進(jìn)行既讀又寫的操作,所以造成內(nèi)存沖突。
上例代碼解決內(nèi)存沖突:
var step = 1
func increment(_ num: inout Int) {
num += step
}
var temp = step
increment(&temp)
step = temp
下面代碼也是存在內(nèi)存沖突:
func sum(_ x: inout Int, _ y: inout Int) {
x = x + y
}
struct AA {
var x: Int = 0
var y: Int = 0
mutating func add(a: inout AA) {
sum(&a.x, &a.y)
}
}
var aa = AA(x: 1, y: 2)
var bb = AA(x: 1, y: 3)
sum(&aa.x, &aa.y) //編譯沒(méi)問(wèn)題,但內(nèi)存沖突,運(yùn)行報(bào)錯(cuò)Simultaneous accesses to 0x100008190, but modification requires exclusive access.
這句語(yǔ)句運(yùn)行報(bào)錯(cuò),是因?yàn)?code>aa.x aa.y
雖然是兩個(gè)不同的變量,內(nèi)存地址也不一樣,但是它們是一個(gè)整體,都在結(jié)構(gòu)體實(shí)例aa
的內(nèi)存空間內(nèi),訪問(wèn)它們兩個(gè)也就是同時(shí)訪問(wèn)同一個(gè)結(jié)構(gòu)體內(nèi)存。
所以上面代碼存在內(nèi)存訪問(wèn)沖突。
元組也一樣,元組內(nèi)的不同變量訪問(wèn),其實(shí)也是訪問(wèn)同一塊元組內(nèi)存,只是它們內(nèi)部變量的地址不同而已,外部存儲(chǔ)變量的元組內(nèi)存空間還是同一份。
- 如果下面條件可以滿足,說(shuō)明重疊訪問(wèn)結(jié)構(gòu)體的屬性是安全的
- 只訪問(wèn)實(shí)例存儲(chǔ)屬性,不是計(jì)算屬性或者類屬性
- 結(jié)構(gòu)體是局部變量而非全局變量
- 結(jié)構(gòu)體要么沒(méi)有被閉包捕獲要么只被非逃逸閉包捕獲
//沒(méi)問(wèn)題
func test() {
var aa = AA(x: 1, y: 2)
sum(&aa.x, &aa.y)
}
指針
- Swift中有專門的指針類型,這些都被定性為不安全的(Unsafe),常見(jiàn)有以下4種:
UnsafePointer<Pointee>
類似const Pointee *
(<Pointee>
代表泛型)UnsafeMutablePointer<Pointee>
類似Pointee *
UnsafeRawPointer
類似const void *
UnsafeMutableRawPointer
類似void *