Comptage automatique de références Swift (ARC)

Swift utilise l'Automatic Reference Counting (ARC) pour suivre et gérer l'utilisation de la mémoire de votre application.

Dans la plupart des cas, nous n'avons pas besoin de libérer manuellement la mémoire, car ARC libère automatiquement la mémoire occupée par une instance de classe lorsque cette instance n'est plus utilisée.

Mais dans certains cas, nous devons encore implémenter la gestion de la mémoire dans le code.

Comment fonctionne ARC

  • Chaque fois que vous utilisez init() pour créer une nouvelle instance d'une classe, ARC alloue un bloc de mémoire pour stocker des informations sur l'instance.

  • Cette mémoire contient les informations de type de l'instance, ainsi que les valeurs de toutes ses propriétés associées.

  • Lorsque l'instance n'est plus utilisée, ARC libère la mémoire occupée par l'instance et rend la mémoire libérée disponible pour d'autres usages.

  • Pour garantir que les instances en cours d'utilisation ne soient pas détruites, ARC suit et calcule le nombre de propriétés, constantes et variables qui font actuellement référence à chaque instance.

  • Lorsqu'une instance est affectée à une propriété, une constante ou une variable, celles-ci créent une référence forte vers l'instance. Tant qu'une référence forte existe encore, l'instance ne peut pas être détruite.

Exemple d'ARC

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

La sortie du programme ci-dessus est :

Example 开始初始化
Example 被析构

Cycles de références fortes entre instances de classe

Dans l'exemple ci-dessus, ARC suit le nombre de références de l'instance Person que vous créez et la détruit lorsque l'instance Person n'est plus nécessaire.

Cependant, nous pourrions écrire du code où une classe n'a jamais zéro référence forte. Cela se produit lorsque deux instances de classe détiennent des références fortes l'une envers l'autre, s'empêchant mutuellement d'être détruites. C'est ce qu'on appelle un cycle de références fortes.

Exemple

Ce qui suit montre un exemple de création accidentelle d'un cycle de références fortes. L'exemple définit deux classes : Person et Apartment, qui modélisent un appartement et ses résidents :

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

Résolution des cycles de références fortes entre instances de classe

Swift propose deux façons de résoudre le problème des cycles de références fortes que vous rencontrez lors de l'utilisation de propriétés de classe :

  • Références faibles
  • Références non détenues

Les références faibles et non détenues permettent à une instance d'un cycle de références de faire référence à une autre instance sans conserver une référence forte. Cela permet aux instances de se référer mutuellement sans créer de cycle de références fortes.

Utilisez une référence faible pour les instances qui peuvent devenir nil au cours de leur durée de vie. À l'inverse, utilisez une référence non détenue pour les instances qui, une fois assignées lors de l'initialisation, ne seront plus jamais définies à nil.

Exemple de référence faible

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

La sortie du programme ci-dessus est :

ARC 主模块
子模块 topic 数为 4

Exemple de référence non détenue

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

La sortie du programme ci-dessus est :

ARC
学生的分数为 98

Cycles de références fortes causés par les fermetures

Un cycle de références fortes peut également se produire lorsque vous affectez une fermeture à une propriété d'une instance de classe, et que le corps de la fermeture utilise l'instance. Le corps de la fermeture peut accéder à une propriété de l'instance, telle que self.someProperty, ou appeler une méthode de l'instance, telle que self.someMethod. Dans les deux cas, la fermeture « capture » self, ce qui entraîne un cycle de références fortes.

Exemple

L'exemple suivant montre comment un cycle de références fortes est créé lorsqu'une fermeture fait référence à self. L'exemple définit une classe appelée HTMLElement, qui modélise un élément HTML unique avec un modèle simple :

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

La classe HTMLElement crée un cycle de références fortes entre l'instance de classe et la fermeture utilisée comme valeur par défaut de asHTML.

La propriété asHTML de l'instance détient une référence forte vers la fermeture. Cependant, la fermeture utilise self dans son corps (en référençant self.name et self.text), donc la fermeture capture self, ce qui signifie que la fermeture détient à son tour une référence forte vers l'instance HTMLElement. Cela crée un cycle de références fortes entre les deux objets.

Résoudre les cycles de références fortes causés par les fermetures : définissez une liste de capture dans le cadre de la fermeture lors de la définition de la fermeture. De cette façon, vous pouvez résoudre le cycle de références fortes entre la fermeture et l'instance de classe.


Références faibles et non détenues

Lorsque la fermeture et l'instance capturée se réfèrent toujours l'une à l'autre et sont toujours détruites en même temps, définissez la capture dans la fermeture comme une référence non détenue.

Inversement, lorsque la référence capturée peut parfois être nil, définissez la capture dans la fermeture comme une référence faible.

Si la référence capturée ne sera jamais définie sur nil, vous devez utiliser une référence non détenue plutôt qu'une référence faible.

Exemple

Dans l'exemple HTMLElement précédent, une référence non détenue est la bonne façon de résoudre le cycle de références fortes. Écrivez la classe HTMLElement de cette manière pour éviter le cycle de références fortes :

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

La sortie du programme ci-dessus est :

<p>hello, world</p>
p 被析构
Autres extensions