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 를 붙이게 된다.
그리고 거의 대부분은 그게 올바른 선택이다.
다음과 같은 코드가 있다. [weak self] 를 하지 않고 있다. 약참조 캡처링을 하지 않고 있다.
UIView.animate(withDuration: 1,
animations: { self.label.alpha = 0.5},
completion: { _ in self.value += 1 })
하지만 이 코드는 메모리 누수가 일어나지 않는다.
UIView . animate 이기 때문이다. UIView 라는 타입에 대고 메서드를 호출하고 있다.
static 한 호출을 하고 있기 때문에 self 의 reference count 는 증가하지 않는다.
아래 사진은 UIView.animate 의 정의부를 캡처한 것이다.
class function (타입 메서드)이다.
UIView 의 인스턴스 self 가 어떤 것이던 무관하게 동작하는 (= static 한) 메서드이다.
또한, 이런 상황에서는 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
}
}
}
// 이후 원하는 결과 출력됨.