Swift 자동 참조 카운팅


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개 없도록 코드를 작성할 수도 있습니다. 이는 두 클래스 인스턴스가 서로에 대한 강력한 참조를 유지하고 서로가 파괴되는 것을 방지할 때 발생합니다. 이를 강한 참조 순환이라고 합니다.

Example

다음은 실수로 강력한 참조 순환을 생성한 예를 보여줍니다. 이 예제에서는 아파트와 거주자를 모델링하는 데 사용되는 Person 및 Apartment라는 두 가지 클래스를 정의합니다.

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

인스턴스 간 강력한 참조 순환 해결

Swift는 클래스 속성을 사용할 때 문제를 해결하는 두 가지 방법을 제공합니다. 순환 강한 참조 문제는 다음과 같은 경우에 발생합니다.

  • 약한 참조

  • Unowned 참조

약한 참조 및 소유되지 않은 참조를 사용하면 순환 참조의 한 인스턴스가 강력한 참조를 유지하지 않고 다른 인스턴스를 참조할 수 있습니다. 이러한 방식으로 인스턴스는 강력한 참조 순환을 만들지 않고도 서로를 참조할 수 있습니다.

수명 동안 0이 될 인스턴스에는 약한 참조를 사용하세요. 반대로, 초기 할당 후 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

Unowned 참조 인스턴스

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

위 프로그램 실행의 출력 결과는 다음과 같습니다.

ARC
学生的分数为 98

다음으로 인한 강력한 참조 순환 closure

강한 참조 순환은 계속 발생합니다. 클래스 인스턴스의 속성에 클로저를 할당하고 해당 인스턴스가 클로저 본문에 사용될 때 발생합니다. 이 클로저 본문은 self.someProperty와 같은 인스턴스의 속성에 액세스하거나 클로저에서 self.someMethod와 같은 인스턴스의 메서드를 호출할 수 있습니다. 두 경우 모두 클로저가 자체를 "캡처"하여 강력한 참조 순환을 생성하게 됩니다.

Example

다음 예는 클로저가 self를 참조할 때 강력한 참조 순환을 생성하는 방법을 보여줍니다. 이 예제에서는 간단한 모델을 사용하여 HTML의 단일 요소를 나타내는 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())

HTMLElement 클래스는 클래스 인스턴스와 asHTML 기본값의 클로저 사이에 강력한 순환 참조를 생성합니다.

인스턴스의 asHTML 속성은 클로저에 대한 강력한 참조를 보유합니다. 그러나 클로저는 클로저 본문(self.name 및 self.text 참조)에서 self를 사용하므로 클로저는 self를 캡처합니다. 이는 클로저가 HTMLElement 인스턴스에 대한 강력한 참조를 보유한다는 의미입니다. 이러한 방식으로 두 개체는 강력한 순환 참조를 갖게 됩니다.

클로저로 인한 강력한 참조 순환 해결: 클로저를 정의할 때 캡처 목록을 클로저의 일부로 정의하면 클로저와 클래스 인스턴스 간의 강력한 참조 순환을 해결할 수 있습니다.


약한 참조 및 소유되지 않은 참조

클로저와 캡처된 인스턴스가 항상 서로를 참조하고 항상 동시에 소멸되는 경우 클로저 내의 캡처를 소유되지 않은 참조로 정의하세요.

반대로 캡처 참조가 때때로 0일 수 있는 경우 클로저 내의 캡처를 약한 참조로 정의하세요.

캡처된 참조가 절대 nil로 설정되지 않는 경우 약한 참조 대신 소유되지 않은 참조를 사용해야 합니다.

이전 HTMLElement 예에서는 소유되지 않은 참조가 강력한 참조 순환을 해결하는 올바른 방법입니다. 강력한 참조 순환을 피하기 위해 다음과 같이 HTMLElement 클래스를 작성하십시오.

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

위 프로그램 실행의 출력 결과는 다음과 같습니다.

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