Swift 內(nèi)存管理

  • 跟OC一樣,Swift也是采用基于引用計(jì)數(shù)的ARC內(nèi)存管理方案(針對(duì)堆空間)

  • Swift的ARC中有三種引用

  1. 強(qiáng)應(yīng)用(strong reference):默認(rèn)情況下,引用都是強(qiáng)引用
  1. 弱引用(weak reference):通過(guò)weak定義弱引用
  • 必須是可選類型的var,因?yàn)閷?shí)例銷毀后,ARC會(huì)自動(dòng)將弱引用設(shè)置為nil
  • ARC自動(dòng)給弱引用設(shè)置nil時(shí),不會(huì)觸發(fā)屬性觀察器
  1. 無(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)去掉,c3c6是編譯不通過(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,所以,性能上多了一丟丟消耗

  1. 在生命周期中可能會(huì)變?yōu)?code>nil的對(duì)象,使用weak
  2. 初始化賦值后再也不會(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):

強(qiáng)應(yīng)用下引用計(jì)數(shù)器變化

我們注釋掉上面代碼里的p.fn = { p.run() },再看匯編:
注釋后的引用計(jì)數(shù)

可以看出,p.fn = { p.run() }里,閉包對(duì)p對(duì)象進(jìn)行了強(qiáng)引用也就是retain操作,構(gòu)成了引用計(jì)數(shù)始終為1的情況,無(wú)法釋放對(duì)象。

  • 在閉包表達(dá)式的捕獲列表聲明weakunowned引用,解決循環(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修飾pp后面不用跟?,因?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ā)生:
  1. 至少一個(gè)是寫入操作
  2. 它們?cè)L問(wèn)的是同一塊內(nèi)存
  3. 它們的訪問(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)體的屬性是安全的
  1. 只訪問(wèn)實(shí)例存儲(chǔ)屬性,不是計(jì)算屬性或者類屬性
  2. 結(jié)構(gòu)體是局部變量而非全局變量
  3. 結(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種:
  1. UnsafePointer<Pointee>類似const Pointee *<Pointee>代表泛型)
  2. UnsafeMutablePointer<Pointee>類似Pointee *
  3. UnsafeRawPointer類似const void *
  4. UnsafeMutableRawPointer類似void *
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,115評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 177,577評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,514評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,234評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,621評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,822評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,380評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,128評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,319評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,548評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,970評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,229評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,048評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,285評(píng)論 2 376