swift ARC 對(duì)象引用之間的幾種關(guān)系

ARCAutomatic Reference Counts 自動(dòng)引用計(jì)數(shù),可以用于對(duì)象的自動(dòng)回收。

示例:定義一個(gè)User類,在初始化和銷毀時(shí)輸出打印語(yǔ)句

class User {
    var name: String
    
    init(name: String) {
        self.name = name
        
        print("初始化 \(name)")
    }
    
    deinit {
        print("銷毀 \(name)")
    }
}

// 使用
let user = User(name: "John")
// 打印結(jié)果
初始化 John

因?yàn)?strong>user始終在在作用域內(nèi),所以 deinit 不會(huì)被調(diào)用,可以將上面的語(yǔ)句包含在 do 語(yǔ)句中,這樣do語(yǔ)句完成后,就跳出該作用域了

do {
    let user = User(name: "John")
}

// 打印結(jié)果
初始化 John
銷毀 John

這個(gè)例子主要是想表達(dá)一個(gè)對(duì)象正常情況下是如何初始化,如何被銷毀的

1.一個(gè)對(duì)象的生命周期

swift中對(duì)象的生命周期由5個(gè)階段組成:

  1. Allocation: 從棧或堆中分配內(nèi)存
  2. Initialization: init 初始化
  3. Usage:使用
  4. Deinitialization: deinit 銷毀
  5. Deallocation: 將內(nèi)存返回給?;蚨眩M(jìn)行回收

Reference counts 也稱為 引用計(jì)數(shù),這個(gè)數(shù)量決定一個(gè)對(duì)象是否再需要,如果引用為0,則對(duì)象不再需要,會(huì)被銷毀。

上面示例中的 user 對(duì)象,引用計(jì)數(shù)如下:

1.arc示例圖.png

2.引用循環(huán)(Reference Cycles)

大多數(shù)情況下,ARC會(huì)幫我們自動(dòng)處理對(duì)象的銷毀工作。

但是假如2個(gè)對(duì)象相互引用,因?yàn)?個(gè)對(duì)象的引用計(jì)數(shù)始終不為0,因此不會(huì)被銷毀。

2.ReferenceCycle.png

上面圖示是一種 強(qiáng)引用循環(huán)(strong reference cycle)。

可以看出 object1object2 相互引用,變量銷毀后,它們引用計(jì)數(shù)為 1, 因此2個(gè)對(duì)象不會(huì)被銷毀。

3.檢查你的引用(Checking Your References)

接著上面的例子,創(chuàng)建一個(gè) Phone

class User {
    var name: String
    // 用戶擁有的手機(jī)
    private(set) var phones: [Phone] = []
    
    init(name: String) {
        self.name = name
        
        print("初始化 \(name)")
    }
    
    func addPhone(phone: Phone) {
        phones.append(phone)
        // 手機(jī)的持有者
        phone.owner = self
    }
    
    deinit {
        print("銷毀 \(name)")
    }
}

class Phone {
    let model: String
    // 手機(jī)的主人
    var owner: User?
    
    init(model: String) {
        self.model = model
        
        print("手機(jī)模型 \(model)")
    }
    
    deinit {
        print("銷毀手機(jī)模型 \(model)")
    }
}

do {
  let user = User(name: "John")
  let iphone = Phone(model: "iphone6s")
  user.addPhone(phone: iphone)
}

Playground 中運(yùn)行上面的代碼,會(huì)打印

初始化 John
手機(jī)模型 iphone6s

會(huì)發(fā)現(xiàn) useriphone 并沒有被回收,兩者之間建立了強(qiáng)引用循環(huán),導(dǎo)致對(duì)象不會(huì)被回收

3.UserIphoneCycle.png

4. 弱引用(Weak References)

為了打破強(qiáng)引用循環(huán),可以將相互引用的對(duì)象之間的關(guān)系設(shè)置為 弱(weak)。

除非特別的指定,所有的引用都是強(qiáng)引用,會(huì)影響引用計(jì)數(shù)。弱引用則不會(huì)增加對(duì)象的引用計(jì)數(shù),另外:

  • 弱引用永遠(yuǎn)都要聲明為可選類型,因此必須使用 var 聲明
  • 當(dāng)引用數(shù)變?yōu)?時(shí),引用會(huì)自動(dòng)的設(shè)置為 nil
4.WeakReference.png

上圖中虛線表示弱引用,實(shí)線表示強(qiáng)引用。 object1 有1個(gè)強(qiáng)引用,一個(gè)弱引用(object2 對(duì) object1 是弱引用), object2 有2個(gè)強(qiáng)引用。

回到 Phone 類中,將 owner 屬性聲明為 weak

class User {
    var name: String
    private(set) var phones: [Phone] = []
    
    init(name: String) {
        self.name = name
        
        print("初始化 \(name)")
    }
    
    func addPhone(phone: Phone) {
        phones.append(phone)
        // 手機(jī)的持有者
        phone.owner = self
    }
    
    deinit {
        print("銷毀 \(name)")
    }
}

class Phone {
    let model: String
    // 將phone對(duì)user的引用設(shè)置為弱引用
    weak var owner: User?
    
    init(model: String) {
        self.model = model
        
        print("手機(jī)模型 \(model)")
    }
    
    deinit {
        print("銷毀手機(jī)模型 \(model)")
    }
}

do {
  let user = User(name: "John")
  let iphone = Phone(model: "iphone6s")
  user.addPhone(phone: iphone)
}

打印結(jié)果

初始化 John
手機(jī)模型 iphone6s
銷毀 John
銷毀手機(jī)模型 iphone6s
5.UserIphoneCycleWeaked.png

5. 無(wú)主引用(Unowned References)

這是另一種不增加引用計(jì)數(shù)的修飾符:unowned.

unownedweak 有什么區(qū)別呢?弱引用必須為可選類型,當(dāng)引用的對(duì)象不存在時(shí),自動(dòng)設(shè)置為 nil; 無(wú)主引用則不能為可選類型,如果你引用一個(gè)已經(jīng)被銷毀的對(duì)象屬性,會(huì)拋出錯(cuò)誤。

6.幾種引用方式.png

在上面的代碼中添加一個(gè) CarrierSubscription (運(yùn)營(yíng)商訂閱者)類

class User {
    var name: String
    private(set) var phones: [Phone] = []
    // 使用的運(yùn)營(yíng)商
    var subscriptions: [CarrierSubscription] = []
    
    init(name: String) {
        self.name = name
        
        print("初始化 \(name)")
    }
    
    func addPhone(phone: Phone) {
        phones.append(phone)
        // 手機(jī)的持有者
        phone.owner = self
    }
    
    deinit {
        print("銷毀 \(name)")
    }
}

class Phone {
    let model: String
    weak var owner: User?
    var carrierSubscription: CarrierSubscription?
    
    init(model: String) {
        self.model = model
        
        print("手機(jī)模型 \(model)")
    }
    
    func provision(carrierSubscription: CarrierSubscription) {
        self.carrierSubscription = carrierSubscription
    }
    
    func decommission() {
        self.carrierSubscription = nil
    }
    
    deinit {
        print("銷毀手機(jī)模型 \(model)")
    }
}

class CarrierSubscription {
    let name: String
    let countryCode: String
    let number: String
    let user: User
    
    init(name: String, countryCode: String, number: String, user: User) {
        // 運(yùn)營(yíng)商名
        self.name = name
        // 區(qū)號(hào)
        self.countryCode = countryCode
        // 電話號(hào)碼
        self.number = number
        // 對(duì)應(yīng)用戶
        self.user = user
        
        // 將 CarrierSubscription 添加到User的subscriptions屬性中
        user.subscriptions.append(self)
        
        print("CarrierSubscription \(name) 初始化")
    }
    
    deinit {
        print("CarrierSubscription \(name) 銷毀")
    }
}

do {
  let user = User(name: "John")
  let iphone = Phone(model: "iphone6s")
  user.addPhone(phone: iphone)
  let subscription = CarrierSubscription(
        name: "TelBel",
        countryCode: "0032",
        number: "31415926",
        user: user)
  iphone.provision(carrierSubscription: subscription)
}

// 打印結(jié)果
初始化 John
手機(jī)模型 iphone6s
CarrierSubscription TelBel 初始化

可以發(fā)現(xiàn) user & iphone & subscription 最后都沒有被回收.

它們3者之間的引用關(guān)系如下

7.Reference-Cycle.png

打破這種關(guān)系(Break the Chain)

一個(gè) User 可以不需要一個(gè) CarrierSubscription 存在 ,但是,運(yùn)營(yíng)商不能沒有 User 的情況下存在,因此 CarrierSubscriptionuser 應(yīng)該是 無(wú)主的 (unowned)

CarrierSubscriptionuser 進(jìn)行更改

let user: User

// 更改為
unowned let user: User

然后運(yùn)行代碼, 打印結(jié)果如下,可以看出3個(gè)對(duì)象都被銷毀了:

初始化 John
手機(jī)模型 iphone6s
CarrierSubscription TelBel 初始化
銷毀 John
銷毀手機(jī)模型 iphone6s
CarrierSubscription TelBel 銷毀

引用關(guān)系圖如下:

8.使用Unowned.png

6. 閉包引起的引用循環(huán)(Reference Cycles with Closures)

因?yàn)殚]包也是 引用類型,因此也會(huì)導(dǎo)致引用循環(huán)的問(wèn)題。

比如,如果你將閉包賦值給一個(gè)類中的某個(gè)屬性,該閉包使用到了實(shí)例的某些屬性,這樣就會(huì)導(dǎo)致引用循環(huán)。即:對(duì)象通過(guò)一個(gè)存儲(chǔ)屬性引用一個(gè)閉包,閉包通過(guò) self 的捕獲值(captured value) 引用了對(duì)象。

9.Closure-Referene-1.png

在上面的 CarrierSubscription 中添加一個(gè)屬性

// completePhoneNumber是存儲(chǔ)屬性 它引用了一個(gè)閉包
lazy var completePhoneNumber: () -> String = {
    // 閉包內(nèi)部引用了對(duì)象的某些屬性
        return self.countryCode + " " + self.number
    }

do 語(yǔ)句中添加

print(subscription.completePhoneNumber())

即:

class User {
    var name: String
    private(set) var phones: [Phone] = []
    // 使用的運(yùn)營(yíng)商
    var subscriptions: [CarrierSubscription] = []
    
    init(name: String) {
        self.name = name
        
        print("初始化 \(name)")
    }
    
    func addPhone(phone: Phone) {
        phones.append(phone)
        // 手機(jī)的持有者
        phone.owner = self
    }
    
    deinit {
        print("銷毀 \(name)")
    }
}

class Phone {
    let model: String
    weak var owner: User?
    var carrierSubscription: CarrierSubscription?
    
    init(model: String) {
        self.model = model
        
        print("手機(jī)模型 \(model)")
    }
    
    func provision(carrierSubscription: CarrierSubscription) {
        self.carrierSubscription = carrierSubscription
    }
    
    func decommission() {
        self.carrierSubscription = nil
    }
    
    deinit {
        print("銷毀手機(jī)模型 \(model)")
    }
}

class CarrierSubscription {
    let name: String
    let countryCode: String
    let number: String
    unowned let user: User
    
    lazy var completePhoneNumber: () -> String = {
        return self.countryCode + " " + self.number
    }
    
    init(name: String, countryCode: String, number: String, user: User) {
        // 運(yùn)營(yíng)商名
        self.name = name
        // 區(qū)號(hào)
        self.countryCode = countryCode
        // 電話號(hào)碼
        self.number = number
        // 對(duì)應(yīng)用戶
        self.user = user
        
        // 將 CarrierSubscription 添加到User的subscriptions屬性中
        user.subscriptions.append(self)
        
        print("CarrierSubscription \(name) 初始化")
    }
    
    deinit {
        print("CarrierSubscription \(name) 銷毀")
    }
}

do {
  let user = User(name: "John")
  let iphone = Phone(model: "iphone6s")
  user.addPhone(phone: iphone)
  let subscription = CarrierSubscription(
        name: "TelBel",
        countryCode: "0032",
        number: "31415926",
        user: user)
  iphone.provision(carrierSubscription: subscription)

  print(subscription.completePhoneNumber())
}


// 打印結(jié)果
初始化 John
手機(jī)模型 iphone6s
CarrierSubscription TelBel 初始化
0032 31415926
銷毀 John
銷毀手機(jī)模型 iphone6s

可以發(fā)現(xiàn) user & iphone 對(duì)象都被回收了,但是 subscription 沒有。這是由于對(duì)象和閉包之間的強(qiáng)引用:

10.Closure and strong reference with object.png

捕獲列表(Capture Lists)

swift 有一種簡(jiǎn)潔而且優(yōu)雅的方式解決閉包中的強(qiáng)引用。 在閉包中聲明一個(gè)捕獲列表,用來(lái)定義它和捕獲對(duì)象之間的關(guān)系

簡(jiǎn)單示例:

var x = 5
var y = 5

// 將 x 放入到捕獲列表中
// 這樣可以在閉包定義的位置對(duì)x的值進(jìn)行捕獲, 這里x的值為5
// 捕獲是通過(guò)值進(jìn)行的 而不是引用
let someClosure = { [x] in
  print("\(x), \(y)")
}

x = 6
y = 6
someClosure() // 打印 5,6
print("\(x), \(y)") // 打印 6,6

捕獲列表在閉包中對(duì)使用的對(duì)象定義 weak 或者 unowned 關(guān)系使用方便。

在上面的 CarrierSubscription 示例中,使用 unowned 定義捕獲列表比較合適,因?yàn)殚]包在 CarrierSubscription 銷毀后也不存在了。

因此將上面的閉包更改:

lazy var completePhoneNumber: () -> String = {
    return self.countryCode + " " + self.number
}

// 更改為
// 對(duì) self 進(jìn)行捕獲
// 關(guān)系為 unowned 這樣CarrierSubscription銷毀后 閉包也不復(fù)存在
lazy var completePhoneNumber: () -> String = { [unowned self] in
    return self.countryCode + " " + self.number
}

// 打印結(jié)果
初始化 John
手機(jī)模型 iphone6s
CarrierSubscription TelBel 初始化
0032 31415926
銷毀 John
銷毀手機(jī)模型 iphone6s
CarrierSubscription TelBel 銷毀

可以看出3個(gè)對(duì)象都被銷毀了。

上面的語(yǔ)法實(shí)質(zhì)上是一種簡(jiǎn)寫:

lazy var completePhoneNumber: () -> String = { [unowned newID = self] in
    return newID.countryCode + " " + newID.number
}

newID 是一個(gè) unowned self 的拷貝。在閉包外的作用域, self 保持自己原有的意義。

6.1 小心使用無(wú)主關(guān)系(Using Unowned with Care)

上面的示例中 selfcompletePhoneNumber 的關(guān)系是 unowned.

如果你確認(rèn)閉包中的引用對(duì)象永遠(yuǎn)不會(huì)被回收,則可以使用 unowned, 如果 self 被回收了,則會(huì)報(bào)錯(cuò)。

比如

class WWDCGreeting {
    let who: String
    
    init(who: String) {
        self.who = who
    }
    
    // 使用 [unowned self] 表示self 和 閉包之間的關(guān)系
    lazy var greetingMaker: () -> String = { [unowned self] in
        return "Hello, \(self.who)"
    }
}

let greetingMaker: () -> String

do {
    let john = WWDCGreeting(who: "John")
    greetingMaker = john.greetingMaker
}
// 報(bào)錯(cuò) 因?yàn)?self.who 已經(jīng)被回收
print(greetingMaker())

直接報(bào)錯(cuò),因?yàn)殚]包期望 self.who 仍然有效,但是當(dāng) john 在作用域之外后,你對(duì)其進(jìn)行了回收。

解決這個(gè)陷阱

使用 weak 替換 unowned

class WWDCGreeting {
    let who: String
    
    init(who: String) {
        self.who = who
    }
    // 將 [unowned self] 替換為 [weak self]
    lazy var greetingMaker: () -> String = { [weak self] in
        // 使用 self?.who 替換 self.who
        return "Hello, \(self?.who)"
    }
}

let greetingMaker: () -> String

do {
    let john = WWDCGreeting(who: "John")
    greetingMaker = john.greetingMaker
}
// 打印 Hello, nil
print(greetingMaker())

對(duì)上面的閉包進(jìn)一步優(yōu)化

lazy var greetingMaker: () -> String = { [weak self] in
  guard let self = self else {
    return "No greeting available."
  }
  return "Hello \(self.who)."
}

7. 值類型循環(huán)和引用類型循環(huán)(Cycles with Value Types and Reference Types)

swift 中class是引用類型,sturct,enum等是值類型。當(dāng)傳遞值類型時(shí),是進(jìn)行拷貝操作,而引用類型則是進(jìn)行共享。

// 錯(cuò)誤用法
// 因?yàn)榻Y(jié)構(gòu)體是值類型,不能遞歸使用自己或者使用自己的實(shí)例
struct Node {
    var payload = 0
    var next: Node? // 使用自己類型 報(bào)錯(cuò)
}

但是對(duì)于class,自我引用不會(huì)報(bào)錯(cuò)

class Person {
    var name: String
    // friends 是 [Person] 類型
    var friends: [Person] = []
    init(name: String) {
        self.name = name
        print("新的 person實(shí)例: \(name)")
    }
    
    deinit {
        print("person實(shí)例 \(name) 被銷毀")
    }
}

do {
    let john = Person(name: "John")
    let harry = Person(name: "Harry")
    
  // 形成循環(huán)引用
  john.friends.append(harry)
  harry.friends.append(john)
}

johnharry 因?yàn)?friends 數(shù)組的原因,導(dǎo)致相互引用,盡管array自身是值類型。如果將 friends 聲明為 unowned let friends, Xcode會(huì)拋出錯(cuò)誤: unowned 只能用于 class types.

為了解決這個(gè)問(wèn)題,需要參加一個(gè)泛型的對(duì)象包裹,然后用它給數(shù)組添加實(shí)例。

class Unowned<T: AnyObject> {
    unowned var value: T
    init(_ value: T) {
        self.value = value
    }
}

class Person {
    var name: String
    // friends 是 Unowned<Person> 類型 對(duì)Person進(jìn)行包裹
    var friends: [Unowned<Person>] = []
    init(name: String) {
        self.name = name
        print("新的 person實(shí)例: \(name)")
    }
    
    deinit {
        print("person實(shí)例 \(name) 被銷毀")
    }
}

do {
    let john = Person(name: "John")
    let harry = Person(name: "Harry")
    
  
  john.friends.append(Unowned(harry))
  harry.friends.append(Unowned(john))
}

這樣所有的對(duì)象都可以被銷毀。

注意,friends不再是一個(gè) Person 獨(dú)享集合,而是一個(gè) Unowned 對(duì)象集合,用于對(duì) Person實(shí)例進(jìn)行包裹。

為了訪問(wèn) Person 對(duì)象,可以使用 Unowned 中定義的 value 屬性

let firstFriendOfHarry = harry.friends.first?.value

這篇文章主要講了以下幾個(gè)知識(shí)點(diǎn):

  • 對(duì)象之間的幾種關(guān)系,強(qiáng),弱,無(wú)主
    • weak
    • unowned
  • 捕獲列表的用法
  • 捕獲列表在閉包中如何消除引用循環(huán)
  • 弱引用關(guān)系的使用方式
    • 一定是可選類型,使用 var 進(jìn)行申明
    • 引用計(jì)數(shù)為0時(shí),自動(dòng)設(shè)置為 nil
  • 無(wú)主引用使用的注意事項(xiàng)
    • 一定要確保使用期間對(duì)象不能被回收
  • 值類型引用循環(huán)和引用類型循環(huán),如何使用 泛型對(duì)象 對(duì)類型進(jìn)行包裝,解決相互引用的問(wèn)題

文章來(lái)源:

2019年05月20日00:43:43

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容