迅速な自動参照カウント


Swift は、自動参照カウント (ARC) メカニズムを使用してアプリケーションのメモリを追跡および管理します

メモリが使用されなくなった場合、ARC はクラスのインスタンスを自動的に解放するため、通常、メモリを手動で解放する必要はありません。占領されている。

しかし、コードにメモリ管理を実装する必要がある場合もあります。

ARC 関数

  • init() メソッドを使用してクラスの新しいインスタンスを作成するたびに、ARC はインスタンス情報を保存するために大量のメモリを割り当てます。

  • メモリには、インスタンスのタイプ情報と、このインスタンスのすべての関連属性の値が含まれます。

  • インスタンスが使用されなくなった場合、ARC はインスタンスによって占有されていたメモリを解放し、解放されたメモリを他の目的に使用できるようにします。

  • 使用中のインスタンスが破棄されないように、ARC は各インスタンスによって参照されているプロパティ、定数、変数の数を追跡し、計算します。

  • インスタンスがプロパティ、定数、または変数に割り当てられると、インスタンスへの強参照が作成されます。強参照が存在する限り、インスタンスを破棄することはできません。

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: "php")


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

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

上記のプログラムの実行の出力結果は次のとおりです:

php 开始初始化
php 被析构

クラス インスタンス間の強力な循環参照

上記の例では、ARC は新しく作成された Person への参照の数を追跡します。インスタンスが不要になった場合は、その Person インスタンスを破棄します。

ただし、クラスの強参照が 0 にならないようなコードを書くこともできます。これは、2 つのクラス インスタンスが相互に強い参照を維持し、相互の破棄を防ぐ場合に発生します。これを強参照サイクルと呼びます。

以下は、強参照サイクルを誤って作成してしまう例を示しています。この例では、アパートとその居住者をモデル化するために使用される 2 つのクラス (Personal と Apartment) を定義します。強参照の問題は次の場合に発生します。

弱参照

  • 非所有参照

  • 弱参照と非所有参照により、循環参照内の 1 つのインスタンスが、強参照を維持せずに別のインスタンスを参照することができます。このようにして、インスタンスは、強い参照サイクルを作成せずに相互に参照できます。

  • 存続期間中に nil になるインスタンスには弱い参照を使用します。逆に、最初の代入後に決して nil に割り当てられないインスタンスには、所有されていない参照を使用します。

弱い参照インスタンス

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 php: Person?
var number73: Apartment?

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

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

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

上記プログラムの実行の出力結果は次のとおりです:

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

所有されていない参照インスタンス

ARC 主模块
子模块 topic 数为 4

上記のプログラムの実行の出力結果は次のとおりです:

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

クロージャ

強力な参照サイクルが引き続き発生する クラス インスタンスのプロパティにクロージャを割り当て、そのインスタンスがクロージャ本体で使用されるときに発生します。このクロージャ本体は、クロージャ内で self.someProperty などのインスタンスのプロパティにアクセスしたり、self.someMethod などのインスタンスのメソッドを呼び出したりすることができます。どちらの場合も、クロージャが自己を「捕捉」し、強力な参照サイクルが作成されます。

次の例は、クロージャが自己を参照するときに強参照サイクルを生成する方法を示しています。この例では、HTMLElement というクラスを定義します。このクラスは、単純なモデルを使用して HTML 内の単一要素を表します。

ARC
学生的分数为 98

HTMLElement クラスは、クラス インスタンスと asHTML デフォルト値のクロージャとの間に強力な循環参照を生成します。

インスタンスの asHTML 属性は、クロージャへの強い参照を保持します。ただし、クロージャはクロージャ本体で self を使用する (self.name と self.text を参照) ため、クロージャは self をキャプチャします。これは、クロージャが HTMLElement インスタンスへの強い参照を保持していることを意味します。このように、2 つのオブジェクトには強力な循環参照が存在します。

クロージャによって引き起こされる強参照サイクルを解決する: クロージャを定義するときに、キャプチャ リストをクロージャの一部として定義することで、クロージャとクラス インスタンス間の強参照サイクルを解決できます。


弱い参照と非所有参照

クロージャとキャプチャされたインスタンスが常に相互参照し、常に同時に破棄される場合、クロージャ内のキャプチャを非所有参照として定義します。

逆に、キャプチャ参照が nil になる場合がある場合は、クロージャ内のキャプチャを弱い参照として定義します。

キャプチャされた参照が決して nil に設定されない場合は、弱い参照の代わりに所有されていない参照を使用する必要があります。

前の HTMLElement の例では、非所有参照が強参照サイクルを解決する正しい方法です。強い参照サイクルを避けるために、HTMLElement クラスを次のように記述します:

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

上記のプログラムの実行の出力結果は次のとおりです:

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