Pengiraan rujukan automatik pantas


Swift menggunakan mekanisme pengiraan rujukan automatik (ARC) untuk menjejak dan mengurus memori aplikasi

Biasanya kita tidak perlu melepaskan memori secara manual, kerana ARC akan digunakan apabila kejadian kelas tidak Apabila digunakan semula, memori yang diduduki olehnya dilepaskan secara automatik.

Tetapi kadangkala kita masih perlu melaksanakan pengurusan memori dalam kod.

Fungsi ARC

  • Setiap kali anda menggunakan kaedah init() untuk mencipta tika baharu kelas, ARC akan memperuntukkan sekeping memori yang besar untuk menyimpan tika itu. maklumat.

  • Memori akan mengandungi maklumat jenis kejadian dan nilai semua atribut yang berkaitan dengan kejadian ini.

  • Apabila tika tidak lagi digunakan, ARC melepaskan memori yang diduduki oleh tika itu dan membenarkan memori yang dikeluarkan digunakan untuk tujuan lain.

  • Untuk memastikan tika yang digunakan tidak akan dimusnahkan, ARC akan menjejak dan mengira bilangan sifat, pemalar dan pembolehubah yang dirujuk oleh setiap tika.

  • Menetapkan tika kepada sifat, pemalar atau pembolehubah akan mencipta rujukan kukuh kepada tika ini selagi rujukan kukuh wujud, tika itu tidak dibenarkan dimusnahkan.

Instance 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

Hasil output pelaksanaan program di atas ialah:

php 开始初始化
php 被析构

Rujukan pekeliling yang kuat antara kejadian kelas

Dalam contoh di atas, ARC akan menjejaki bilangan rujukan kepada tika Orang yang baru anda buat dan memusnahkannya apabila tika Orang itu tidak lagi diperlukan.

Walau bagaimanapun, kami mungkin menulis kod supaya kelas tidak akan mempunyai 0 rujukan kukuh. Ini berlaku apabila dua kejadian kelas menyimpan rujukan yang kukuh antara satu sama lain dan menghalang satu sama lain daripada dimusnahkan. Ini dipanggil kitaran rujukan yang kuat.

Contoh

Berikut menunjukkan contoh mencipta kitaran rujukan yang kukuh secara tidak sengaja. Contoh mentakrifkan dua kelas: Orang dan Pangsapuri, yang digunakan untuk memodelkan apartmen dan penghuninya:

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

Selesaikan rujukan kukuh bulat antara kejadian

Swift menyediakan dua kaedah untuk menyelesaikan kitaran masalah rujukan kuat yang anda hadapi apabila menggunakan atribut kelas:

  • Rujukan lemah

  • Rujukan tidak dimiliki

Rujukan yang lemah dan rujukan yang tidak dimiliki membenarkan satu contoh dalam rujukan pekeliling merujuk kepada contoh lain tanpa mengekalkan rujukan yang kukuh. Dengan cara ini, kejadian boleh merujuk antara satu sama lain tanpa mencipta kitaran rujukan yang kukuh.

Gunakan rujukan yang lemah untuk contoh yang akan menjadi sifar sepanjang hayatnya. Sebaliknya, gunakan rujukan yang tidak dimiliki untuk contoh yang tidak akan diberikan kepada sifar selepas tugasan awal.

Instance rujukan yang lemah

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

Hasil output pelaksanaan program di atas ialah:

ARC 主模块
子模块 topic 数为 4

Instance rujukan tidak dimiliki

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

Hasil output program di atas pelaksanaan ialah:

ARC
学生的分数为 98

Kitaran rujukan kuat yang disebabkan oleh penutupan

Kitaran rujukan kuat juga berlaku apabila anda menetapkan penutupan kepada atribut contoh kelas, dan badan penutup mengandungi Contoh lain digunakan. Badan penutup ini boleh mengakses sifat contoh, seperti self.someProperty, atau memanggil kaedah contoh, seperti self.someMethod, dalam penutupan. Kedua-dua kes mengakibatkan penutupan "menangkap" diri, mewujudkan kitaran rujukan yang kukuh.

Contoh

Contoh berikut menunjukkan kepada anda cara mencipta kitaran rujukan yang kukuh apabila penutupan merujuk kepada diri sendiri. Contoh mentakrifkan kelas yang dipanggil HTMLElement, yang menggunakan model ringkas untuk mewakili satu elemen dalam HTML:

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

Kelas HTMLElement menjana gelung kuat antara contoh kelas dan penutupan nilai lalai asHTML. Atribut asHTML contoh

mempunyai rujukan yang kukuh kepada penutupan. Walau bagaimanapun, penutupan menggunakan diri dalam badan penutupnya (merujuk self.name dan self.text), jadi penutupan menangkap diri, yang bermaksud bahawa penutupan itu pula memegang rujukan yang kuat kepada contoh HTMLElement. Dengan cara ini, kedua-dua objek mempunyai rujukan bulat yang kuat.

Selesaikan kitaran rujukan kukuh yang disebabkan oleh penutupan: Apabila mentakrifkan penutupan, tentukan senarai tangkapan sebagai sebahagian daripada penutupan Dengan cara ini, kitaran rujukan kukuh antara penutupan dan tika kelas boleh diselesaikan.


Rujukan Lemah dan Rujukan Tidak Dimiliki

Takrifkan tangkapan dalam penutupan sebagai tidak dimiliki apabila penutupan dan kejadian yang ditangkap sentiasa merujuk antara satu sama lain dan sentiasa dimusnahkan pada masa yang sama Petikan.

Sebaliknya, apabila rujukan penangkapan kadangkala tiada, tentukan tangkapan dalam penutupan sebagai rujukan yang lemah.

Jika rujukan yang ditangkap tidak akan ditetapkan kepada sifar, anda harus menggunakan rujukan yang tidak dimiliki dan bukannya rujukan yang lemah.

Contoh

Dalam contoh HTMLElement sebelumnya, rujukan yang tidak dimiliki ialah cara yang betul untuk menyelesaikan kitaran rujukan yang kukuh. Tulis kelas HTMLElement seperti ini untuk mengelakkan kitaran rujukan yang kuat:

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

Hasil output pelaksanaan program di atas ialah:

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