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 被析构