Swift는 ARC를 사용해서 앱의 메모리 사용을 관리합니다. 즉, 사용자는 메모리 관리에 대해 고려할 필요가 없으며 ARC가 자동으로 인스턴스가 더 이상 사용되지 않으면 메모리에서 해제해줍니다.
하지만 특별한 경우(Strong Reference Cycles이 발생할 가능성이 있는 경우) ARC를 직접 다뤄야 할 필요가 있습니다.
ARC는 인스턴스가 생성되면 메모리를 할당하고, 인스턴스가 더 이상 사용되지 않으면 메모리에서 해제합니다. 아래 코드는 ARC가 어떻게 동작하는지 보여줍니다.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed") // reference counting +1
// Prints "John Appleseed is being initialized"
reference2 = reference1 // reference counting +1
reference3 = reference1 // reference counting +1
reference1 = nil // reference counting -1
reference2 = nil // reference counting -1
reference3 = nil // reference counting -1
// Prints "John Appleseed is being deinitialized"
Person
인스턴스를 참조하는 모든 변수가 nil
이 될 때 비로소 Person
인스턴스가 더 이상 사용되지 않습니다. 따라서, deinit
이 호출되며 메모리에서 해제됩니다.
두 class의 인스턴스가 서로를 참조하게 되면 Strong Reference Cycle이 발생합니다.
아래의 경우, Person
과 Apartment
의 인스턴스가 할당된 john
과 unit4A
가 nil
이 되어도 reference count가 0이 아니므로 인스턴스가 메모리에서 해제되지 않습니다.
// Here’s an example of how a strong reference cycle
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person? = Person(name: "John Appleseed")
var unit4A: Apartment? = Apartment(unit: "4A")
john!.apartment = unit4A // strong reference
unit4A!.tenant = john // strong reference
john = nil
unit4A = nil
// neither deinitializer was called
// causing a memory leak in your app.
스위프트에서는 class의 프로퍼티가 strong reference cycle을 만드는 것을 피하기 위해 두 가지 방법을 제공합니다.
변수 앞에 weak
를 붙여서 strong reference를 피할 수 있습니다. 참조하는 인스턴스가 해제될 때 weak 변수에 할당된 인스턴스도 ARC에 의해 함께 해제됩니다.
해제된 변수는 nil
이 되기 때문에 weak
는 항상 var
와 함께 선언되어야 합니다.
Property observers aren’t called when ARC sets a weak reference to nil.
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // weak reference
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// Person 인스턴스에 strong reference가 없으므로 deinit called
// Prints "John Appleseed is being deinitialized"
print(unit4A!.tenant) // nil
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
weak
와 달리 unowned
는 항상 값이 있습니다. nil
이 될 수 없으므로, 참조하고 있는 인스턴스가 해제되지 않을 것을 확신하는 경우에만 사용해야 합니다.
아래 예제는 Customer
인스턴스가 해제되면 CreditCard
도 함께 메모리에서 해제됩니다.
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil
// Customer instance에 strong reference된게 없으므로 deinit
// 이어서 CreditCard instance에 strong reference된게 없으므로 deinit
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
weak
와 unowned
모두 일반적인 strong reference cycle을 해결할 수 있습니다.
nil
이 될 수 있고, strong reference cycle 예상된다면 weak
를 사용할 수 있습니다.unowned
는 하나는 nil
이 될 수 있고, 다른 하나는 그렇지 않을 때 사용할 수 있습니다.However, there’s a third scenario, in which both properties should always have a value, and neither property should ever be nil once initialization is complete. In this scenario, it’s useful to combine an unowned property on one class with an implicitly unwrapped optional property on the other class.
만약 두 프로퍼티가 항상 값을 가져야하고 한번 초기화 된 이후에는 nil
이 될 수 없다면 unowned
와 implicitly unwrapped optional을 사용할 수 있습니다.
class Country {
let name: String
var capitalCity: City! // implicitly unwrapped optional
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country // unowned reference
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
Country
의 capitalCity
처럼 implicitly unwrapped optional 타입은 옵셔널과 같이 기본 값(default value)은 nil
이지만 unwrapping 없이 접근할 수 있습니다.
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"
두 class 인스턴스에서 발생하는 strong reference cycle처럼, class 인스턴스와 closure 간에 발생하는 strong reference cycle를 capture list를 작성해서 피할 수 있습니다.
closure의 정의부에서 captured reference가 weak
또는 unowned
reference가 될 수 있도록 capture list를 선언할 수 있습니다.
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) />"
}
}
delegate pattern에서도 strong reference cycle이 발생할 수 있습니다.
class ParentViewController: UIViewController, ChildViewControllerProtocol {
let childViewController = ChildViewController()
func prepareChildViewController(){
childViewController.delegate = self
}
}
protocol ChildViewControllerProtocol {
// important functions
}
class ChildViewController: UIViewController {
var delegate: ChildViewControllerProtocol?
}
childViewController
의 delegate
로 ParentViewController
를 설정했습니다. 이로써 두 viewController는 서로 강한 참조 관계가 됩니다.
strong reference cycle을 피하기 위해delegate
변수를 weak
로 선언해줄 수 있습니다.
weak var delegate: ChildViewControllerProtocol?
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
https://baked-corn.tistory.com/30