Swift4 基礎部分: Automatic Reference Counting(自動引用計數)

本文是學習《The Swift Programming Language》整理的相關隨筆,基本的語法不作介紹,主要介紹Swift中的一些特性或者與OC差異點。

系列文章:

Swift uses Automatic Reference Counting (ARC) to track and 
manage your app’s memory usage. In most cases, this means 
that memory management “just works” in Swift, and you do 
not need to think about memory management yourself. ARC 
automatically frees up the memory used by class instances 
when those instances are no longer needed.
  • Swift中是引用自動引用計數來處理app中的內存占用。也就是說你不需要去考慮app中的內存的管理。當類的實例不再被使用時,ARC會自動釋放其占用的內存。
Reference counting only applies to instances of classes. 
Structures and enumerations are value types, not reference 
types, and are not stored and passed by reference.
  • 引用計數僅僅應用于類的實例。結構體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。

ARC的工作機制(How ARC Works)

Every time you create a new instance of a class, ARC 
allocates a chunk of memory to store information about 
that instance. This memory holds information about the 
type of the instance, together with the values of any 
stored properties associated with that instance. 

Additionally, when an instance is no longer needed, ARC 
frees up the memory used by that instance so that the 
memory can be used for other purposes instead. This 
ensures that class instances do not take up space in 
memory when they are no longer needed.
  • 當你每次創建一個類的新的實例的時候,ARC 會分配一大塊內存用來儲存實例的信息。內存中會包含實例的類型信息,以及這個實例所有相關屬性的值。此外,當實例不再被使用時,ARC會釋放實例所占用的內存,并讓釋放的內存其他使用。這就確保了不再被使用的實例,不會一直占用內存空間。

ARC的實踐(ARC in Action)

例子:

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:"xz");
var reference2: Person? = reference1;
var reference3: Person? = reference1;

reference1 = nil;
reference2 = nil;
reference3 = nil;

執行結果:

xz is being initialized
xz is being deinitialized

類實例之間的循環強引用(Strong Reference Cycles Between Class Instances)

循環引用在OC中也是常見的,直接看一個例子:


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"); }
}

var john: Person?;
var unit4A: Apartment?;

john = Person(name: "John Appleseed");
unit4A = Apartment(unit: "4A");

// 以下是核心引發的例子
john!.apartment = unit4A;
unit4A!.tenant = john;

john = nil;
unit4A = nil;

解決類實例之間的強循環引用(Resolving Strong Reference Cycles Between Class Instances)

Swift provides two ways to resolve strong reference cycles 
when you work with properties of class type: weak 
references and unowned references.
  • Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:弱引用(weak reference)和無主引用(unowned reference)。

弱引用(Weak References)

A weak reference is a reference that does not keep a 
strong hold on the instance it refers to, and so does not 
stop ARC from disposing of the referenced instance. This 
behavior prevents the reference from becoming part of a 
strong reference cycle. You indicate a weak reference by 
placing the weak keyword before a property or variable 
declaration.
  • 弱引用不會牢牢保持住引用的實例,并且不會阻止 ARC 銷毀被引用的實例。這種行為阻止了引用變為循環強引用。聲明屬性或者變量時,在前面加上weak關鍵字表明這是一個弱引用。

直接改寫一下上述的例子:

class Apartment {
    let unit: String;
    init(unit: String) { self.unit = unit; }
    weak var tenant: Person?; // 注意此處
    deinit { print("Apartment \(unit) is being deinitialized"); }
}

執行結果:

John Appleseed is being deinitialized
Apartment 4A is being deinitialized

無主引用(Unowned References)

Like a weak reference, an unowned reference does not keep 
a strong hold on the instance it refers to. Unlike a weak 
reference, however, an unowned reference is used when the 
other instance has the same lifetime or a longer lifetime. 
You indicate an unowned reference by placing the unowned 
keyword before a property or variable declaration.
  • 和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用擁有同樣或者更長的生命周期相對其他的實例。
An unowned reference is expected to always have a value. 
As a result, ARC never sets an unowned reference’s value 
to nil, which means that unowned references are defined 
using nonoptional types.
  • 無主引用一直都是有值的,ARC無法在實例被銷毀后將無主引用設為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: Int;
    unowned let customer: Customer;
    init(number: Int, 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

無主引用以及隱式解析可選屬性(Unowned References and Implicitly Unwrapped Optional Properties)

當相互引用的屬性都不允許為nil時,此時就需要使用無主引用+隱式解析可選屬性。

例子:

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

閉包中的循環強引用(Strong Reference Cycles for Closures)

與OC中的block引起的循環強引用一致,直接看一下例子:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world");
print(paragraph!.asHTML());
paragraph = nil;

執行結果:

<p>hello, world</p>

疑問:

  • 為什么deinit函數沒有執行? 因為asHTML的閉包"捕獲"了self,同時asHTML的屬性持有了閉包的強引用。二者之間產生了循環強引用。

解決閉包引起的循環強引用(Resolving Strong Reference Cycles for Closures)

You resolve a strong reference cycle between a closure and a 
class instance by defining a capture list as part of the 
closure’s definition.
  • 為解決閉包和類實例之間的循環強引用,可以定義閉包時同時定義捕獲列表作為閉包的一部分。

具體捕獲列表的語法參考如下:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}
Define a capture in a closure as an unowned reference when the 
closure and the instance it captures will always refer to each 
other, and will always be deallocated at the same time.

Conversely, define a capture as a weak reference when the 
captured reference may become nil at some point in the future.
  • 將閉包內的捕獲定義為無主引用,當閉包和捕獲的實例總是互相引用時并且總是同時銷毀時。相反的,將閉包內的捕獲定義為弱引用,當捕獲引用有時可能會是nil時。

上述的例子中,顯然無主引用可以解決上述閉包引起的循環強引用問題,具體代碼如下:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
         [unowned self] () ->String in // 可簡寫為[unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world");
print(paragraph!.asHTML());
paragraph = nil;

執行結果:

<p>hello, world</p>
p is being deinitialized
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容