iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 6
0
自我挑戰組

IOS app開發介紹系列 第 6

IOS app開發介紹 - IOS一些重要的概念與機制(2.記憶體管理 )

記憶體管理: Auto Reference Counting

Auto reference counting直接翻成中文就是一個物件被別人參考次數。
所以在IOS中,如果一個物件沒有被其他人參考(reference),就會進行記憶體回收,反之,則不會回收記憶體

NOTE:
Reference counting只應用在class的instance. Structure和Enumeration是pass by value,不是pass by reference(reference types),無須擔心記憶體回收問題

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
    print("\(name) is being deinitialized")
    }
}


var reference1: Person? = Person(name: "Kevin")
var reference2: Person? = reference1

reference1 = nil

大家覺得 Person(name: "Kevin") 這個object會被釋放嗎?
答案是不會,因為這個object被reference1和reference2所參考,但是我們只有把reference1設為空,reference2仍有可能被使用,所以ios不會幫我們釋放 Person(name: "Kevin") 這個object,
只有當我們把"reference2 = nil"加上去,才會執行到Person的解構子(deinit)


第二個狀況是: Strong Reference Cycles Between Class Instances

意即兩個class互有對方的reference,範例如下:

class B {
    var object: A? = nil
    deinit {
        print("B deinit :)")
    }
}

class A {
    var object: B? = nil
    deinit {
        print("A deinit :)")
    }
}

var a: A? = A()
var b: B?  = B()

b?.object = a
a?.object = b
a = nil
b = nil

正常情況下,我們會預期當a,b兩個object被設為nil(空)之後,就會執行到class的解構子(deinit),但是因為a和b這兩個object互為對方的資料成員,a要釋放時,發現我還被b參考到.b要釋放時,發現我還被a參考到.所以即使依序把a和b設為空,但記憶體都不會回收

那我們試著把程式碼改成下面這樣:

class B {
    var object: A? = nil
    deinit {
        print("B deinit :)")
    }
}

class A {
    var object: B? = nil
    deinit {
        print("A deinit :)")
    }
}

var a: A? = A()
var b: B?  = B()

b?.object = a
a?.object = b
a?.object = nil
b = nil  -------------------> (A)
a = nil  -------------------> (B)

我們先釋放a底下的object b,再把a設為空.(A)這行執行完,因為所有持有b的reference都已經清空,所以b能夠正常釋放,接著執行到(B),因為b已經釋放,所以所有持有a的reference也都清空,所以a也能夠正常釋放記憶體

Swift提供兩種方式來解決這個問題:

1. Weak reference
2. Unowned reference


1. Weak reference
雖然之前的做法可以正常釋放記憶體,但是當一個class擁有很多個不同型別的資料成員,這樣每次釋放的時候,都要記得先將底下的資料成員設為空,才不會出問題.
但其實有更好的選擇,只要我們在宣告class底下的資料成員**(p.s 要是可以為nil的資料成員)**時,在前面加上weak關鍵字.例如:

class B {
    weak var object: A? = nil
    deinit {
        print("B deinit :)")
    }
}

當我們要先釋放a時,即便b object還持有a的reference,但因為class B中的object(type A)
這樣ios就會去檢查,原來b和其底下的資料成員和a的關係是弱連結,所以即便那個資料成員還在使用,ios也不會把那個資料成員算入reference count,所以a就能夠正常釋放記憶體.


2. Unowned reference
兩個不同class的instances之間的關係是,一個class的instance life time一定比另一個class的instance的life time來得短或一樣長(即"小於等於"),且比較短instance life time的class持有必定不是nil的另一個class instance
如果看不懂上述解釋的話,舉個簡單的例子,信用卡和人的關係,信用卡的存在時間一定比人的生命短,且信用卡一定有一個持有的人,這時候我們就可以像下面程式碼來定義信用卡和人的class:

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 //<-- customer不能是optional(i.e 不可能為nil)
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

ref: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html


上一篇
IOS app開發介紹 - IOS一些重要的概念與機制(1.App的生命週期與ViewController生命週期 )
下一篇
IOS app開發介紹 - IOS一些重要的概念與機制(3. Closure)
系列文
IOS app開發介紹22

尚未有邦友留言

立即登入留言