Swift 는 ARC 라는 메모리 관리 시스템을 사용한다.
Automatic Reference Counting 이라는 문자 그대로 객체의 Reference 가 몇 번 카운팅 되었는지를 계산하며, Reference Count 가 0 이 되면 메모리에서 삭제된다.
예를 들어, 다음과 같은 코드가 있을 때 SomeClass 에 대한 메모리는 계속 남게된다.
class SomeClass {
// 메모리가 해제 되면 "deinit" print.
deinit { print("deinit") }
}
var someClass1: SomeClass? = SomeClass() // reference count : 1
var someClass2 = someClass1 // reference count : 2
someClass1 = nil // reference count : 1
// refernce count > 0 이므로 "deinit" 은 출력되지 않음.
Swift 는 클로저라는 문법을 지원한다.
클로저 내부에서는 값의 캡처링이 일어나며, 참조 타입이 캡처되면 reference count 가 증가한다.
class SomeClass {
// 메모리가 해제 되면 "deinit" print.
deinit { print("deinit") }
}
var someClass: SomeClass? = SomeClass() // reference count : 1
// 캡처 하면서 rc 증가. reference count : 2
var someClosure: (() -> ())? = { [someClass] in
_ = someClass
}
someClosure?()
someClass = nil // reference count : 1
// refernce count > 0 이므로 "deinit" 은 출력되지 않음.
따라서 클로저에서 캡처링이 일어나면서 rc 가 증가하지 않도록, weak 키워드를 사용한다.
class SomeClass {
// 메모리가 해제 되면 "deinit" print.
deinit { print("deinit") }
}
var someClass: SomeClass? = SomeClass() // reference count : 1
// weak 캡처. 약한 참조.
// rc 증가 하지 않음. reference count : 1
var someClosure: (() -> ())? = { [weak someClass] in
_ = someClass
}
someClosure?()
someClass = nil // reference count : 0
// refernce count == 0 이므로 "deinit" 출력.
이런 상황에서는 weak self 캡처링을 하는 것이 적절하지 않다.
class Food {
let name: String
init(_ name: String) {
self.name = name
}
}
extension Food {
// 클로저 내부에서 weak self 캡처링.
// Food 의 인스턴스가 캡처된다.
func getFoodName() -> (() -> String) {
return { [weak self] in
guard let self else { return "none" }
// Food 의 name 을 return 한다.
return self.name
}
}
}
class Human {
let getFavoriteFood: () -> String
init() {
// Food 의 name 이 "chicken" 으로 세팅 되었고,
// getFavoriteFood 초기화.
self.getFavoriteFood = Food("chicken").getFoodName()
}
}
let human = Human()
print(human.getFavoriteFood())
// "chicken" 이 출력되길 바라지만, "none" 이 출력 된다.
원하지 않는 결과가 나오는 이유는 다음과 같다.
따라서 다음과 같이 캡처링을 해야한다.
extension Food {
func getFavoriteFood() -> (() -> String) {
// self 가 아닌 name 을 캡처한다.
return { [name] in
return name
}
}
}
// 이후 원하는 결과 출력됨.