Swift Automatische Referenzzählung (ARC)

Swift verwendet Automatische Referenzzählung (ARC), um die Speichernutzung Ihrer App zu verfolgen und zu verwalten.

In den meisten Fällen müssen wir den Speicher nicht manuell freigeben, da ARC den von einer Klasseninstanz belegten Speicher automatisch freigibt, wenn diese Instanz nicht mehr verwendet wird.

In manchen Fällen müssen wir jedoch immer noch eine Speicherverwaltung im Code implementieren.

Wie ARC funktioniert

  • Jedes Mal, wenn Sie init() verwenden, um eine neue Instanz einer Klasse zu erstellen, weist ARC einen Speicherbereich zu, um Informationen über die Instanz zu speichern.

  • Dieser Speicher enthält die Typinformationen der Instanz sowie die Werte aller ihrer zugehörigen Eigenschaften.

  • Wenn die Instanz nicht mehr verwendet wird, gibt ARC den von der Instanz belegten Speicher frei und stellt den freigegebenen Speicher für andere Zwecke zur Verfügung.

  • Um sicherzustellen, dass verwendete Instanzen nicht zerstört werden, verfolgt und berechnet ARC, wie viele Eigenschaften, Konstanten und Variablen derzeit auf jede Instanz verweisen.

  • Wenn eine Instanz einer Eigenschaft, Konstante oder Variablen zugewiesen wird, erzeugen diese eine starke Referenz auf die Instanz. Solange eine starke Referenz noch existiert, darf die Instanz nicht zerstört werden.

ARC-Beispiel

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 开始初始化")
    }
    deinit {
        print("\(name) 被析构")
    }
}

// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 创建Person类的新实例
reference1 = Person(name: "Example")


//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1

//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil

Die Ausgabe des obigen Programms ist:

Example 开始初始化
Example 被析构

Starke Referenzzyklen zwischen Klasseninstanzen

Im obigen Beispiel verfolgt ARC die Referenzanzahl der von Ihnen erstellten Person-Instanz und zerstört sie, wenn die Person-Instanz nicht mehr benötigt wird.

Wir könnten jedoch Code schreiben, bei dem eine Klasse niemals null starke Referenzen hat. Dies geschieht, wenn zwei Klasseninstanzen starke Referenzen aufeinander halten und sich gegenseitig daran hindern, zerstört zu werden. Man nennt dies einen starken Referenzzyklus.

Beispiel

Das folgende Beispiel zeigt, wie versehentlich ein starker Referenzzyklus entsteht. Das Beispiel definiert zwei Klassen: Person und Apartment, die eine Wohnung und ihre Bewohner modellieren:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var example: Person?
var number73: Apartment?

// 赋值
example = Person(name: "Example")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 example 和 number73 中的实例
// 循环强引用被创建
example!.apartment = number73
number73!.tenant = example

// 断开 example 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
example = nil
number73 = nil

Auflösen von starken Referenzzyklen zwischen Klasseninstanzen

Swift bietet zwei Möglichkeiten, das Problem starker Referenzzyklen zu lösen, das beim Verwenden von Klasseneigenschaften auftritt:

  • Schwache Referenzen
  • Unowned-Referenzen

Schwache und Unowned-Referenzen ermöglichen es einer Instanz in einem Referenzzyklus, auf eine andere Instanz zu verweisen, ohne eine starke Referenz zu halten. Dadurch können Instanzen aufeinander verweisen, ohne einen starken Referenzzyklus zu erzeugen.

Verwende eine schwache Referenz für Instanzen, die während ihrer Lebensdauer nil werden können. Umgekehrt verwendest du eine Unowned-Referenz für Instanzen, die nach ihrer Zuweisung während der Initialisierung nie wieder auf nil gesetzt werden.

Beispiel für eine schwache Referenz

class Module {
    let name: String
    init(name: String) { self.name = name }
    var sub: SubModule?
    deinit { print("\(name) 主模块") }
}

class SubModule {
    let number: Int
    
    init(number: Int) { self.number = number }
    
    weak var topic: Module?
    
    deinit { print("子模块 topic 数为 \(number)") }
}

var toc: Module?
var list: SubModule?
toc = Module(name: "ARC")
list = SubModule(number: 4)
toc!.sub = list
list!.topic = toc

toc = nil
list = nil

Die Ausgabe des obigen Programms ist:

ARC 主模块
子模块 topic 数为 4

Beispiel für eine Unowned-Referenz

class Student {
    let name: String
    var section: Marks?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name)") }
}
class Marks {
    let marks: Int
    unowned let stname: Student
    
    init(marks: Int, stname: Student) {
        self.marks = marks
        self.stname = stname
    }
    
    deinit { print("学生的分数为 \(marks)") }
}

var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil

Die Ausgabe des obigen Programms ist:

ARC
学生的分数为 98

Durch Closures verursachte starke Referenzzyklen

Ein starker Referenzzyklus kann auch auftreten, wenn du einem Property einer Klasseninstanz einen Closure zuweist und der Closure-Body die Instanz verwendet. Der Closure-Body kann auf ein Property der Instanz zugreifen, z. B. self.someProperty, oder eine Methode der Instanz aufrufen, z. B. self.someMethod. In beiden Fällen „erfasst“ der Closure self, was zu einem starken Referenzzyklus führt.

Beispiel

Das folgende Beispiel zeigt, wie ein starker Referenzzyklus entsteht, wenn eine Closure self referenziert. Das Beispiel definiert eine Klasse namens HTMLElement, die ein einzelnes HTML-Element mit einem einfachen Modell modelliert:

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())

Die Klasse HTMLElement erzeugt einen starken Referenzzyklus zwischen der Klasseninstanz und der Closure, die als Standardwert von asHTML verwendet wird.

Das Property asHTML der Instanz hält eine starke Referenz auf die Closure. Die Closure verwendet jedoch self in ihrem Body (mit Verweisen auf self.name und self.text), sodass die Closure self erfasst, was wiederum bedeutet, dass die Closure eine starke Referenz auf die HTMLElement-Instanz hält. Dadurch entsteht ein starker Referenzzyklus zwischen den beiden Objekten.

Auflösen starker Referenzzyklen, die durch Closures verursacht werden: Definiere eine Capture-Liste als Teil der Closure, wenn du die Closure definierst. Auf diese Weise kannst du den starken Referenzzyklus zwischen der Closure und der Klasseninstanz auflösen.


Schwache und Unowned-Referenzen

Wenn die Closure und die erfasste Instanz immer aufeinander verweisen und immer gleichzeitig zerstört werden, definiere die Erfassung in der Closure als eine unowned-Referenz.

Umgekehrt: Wenn die erfasste Referenz manchmal nil sein kann, definiere die Erfassung in der Closure als eine weak-Referenz.

Wenn die erfasste Referenz niemals auf nil gesetzt wird, solltest du eine unowned-Referenz anstelle einer weak-Referenz verwenden.

Beispiel

Im vorherigen HTMLElement-Beispiel ist eine unowned-Referenz der richtige Weg, um den starken Referenzzyklus aufzulösen. Schreibe die HTMLElement-Klasse auf diese Weise, um den starken Referenzzyklus zu vermeiden:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [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) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

Die Ausgabe des obigen Programms ist:

<p>hello, world</p>
p 被析构
Andere Erweiterungen