취업을 한 지인이 면접을 본 회사분이 말씀하시길 weak self를 굳이 쓰지않아도 되는 경우가 있다는걸 말씀해주셨다고한다
항상 클로저내부에서 self를 이용하여 외부프로퍼티를 참조하는 경우에 매번 weak self 혹은 withUnretained 오퍼레이터를 이용하던 나에게 꽤 큰 충격이였다
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
위 코드는 자주 사용되는 고차함수인 map의 시그니처이다. 우린 종종 map을 이용할때 weak self를 사용하곤한다
하지만 map의 시그니처코드를 보면 알 수 있듯이 해당 함수는 @escaping 어노테이션이 붙어있지않음을 확인할 수 있다. 즉 non-escaping 함수라는 것이다
non-escaping 함수라는건 컴파일러는 해당 함수가 종료되고나서 위 클로저가 사용되지않음을 확인한다
-> 즉 map이 종료된 후, 다시 map을 호출한 곳으로 돌아오면 인자로 전달한 클로져는 더 이상 메모리에 올라가 있지 않을 것이라는 것을 의미
-> 애초에 non-escaping함수는 순환참조자체를 만들어낼 수 없는것이다
public func asyncAfter(deadline: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping @convention(block) () -> Voi
DispatchQueue를 이용해서 종종 사용되는 asyncAfter의 시그니처이다. 해당 함수는 보이는것처럼 @escaping을 사용되었다
이런 Escaping클로저에선 다음의 경우에 weak self를 사용하지않으면 순환참조문제가 발생할 수 있다
1. 클로져가 객체의 property에 저장되거나 다른 클로저로 전달될 경우
2. 클로져 안에 있는 객체가 클로져(해당 클로져 혹은 전달 받은 클로져)에 대한 강한 참조를 유지하는 경우
GCD, UIView.animate, UIViewPropertyAnimator과 같이 animation call은 특정 프로퍼티에 저장하지않는 이상 순환참조문제를 발생시키지않는다
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
let formatted = self?.format(42)
print(formatted as Any)
}
정말 신기했던 부분은 그럼 GCD클로저 내부에서 weak self를 사용하지않아도되는데 weak self를 사용할때랑 안할때랑 결과값이 다를 수도 있다는것이다
지금 2초를 더 주었기때문에 2초동안 해당 클로저가 죽지않고 살아있을텐데 해당 인스턴스가 2초 이전에 deinit된다면 self가 nil이 될테고 우리는 의도한 값을 얻을 수 없을 것이다
반대로 weak self를 사용하지않는다면 2초 이전에 해당 인스턴스가 deinit이 되더라도 self는 무조건 인스턴스를 바라보고있고 우리가 의도한 값을 가져올 수 있다는 것이다 와...
정말 코딩을 하면서 무수히 많이 사용했던 코드라 하면은
guard let self = self else { return }
위 코드일것이다
헌데 이런 코드가 부작용이 일어날 수 있음을 야기한다고한다. 그 이유가 무엇일까 ??
guard let self는 인스턴스가 nil인지 확인한 후 nil이 아니라면 클로저가 실행되는 동안 잠시 strong reference와 동일한 효과를 내어 self가 유지됨을 보장한다
옵셔널 체이닝은 모든 메서드 호출에서 self에 대한 nil 검사를 진행한다.
즉 클로저 실행 중 어느 시점에서 self가 nil이 되면 자동으로 해당 메서드 호출을 건너뛰고 다음 줄로 이동한다
-> 결국 guard let self는 deallocated를 지연시킬 수 있다는 점이다
참고사이트 [weak self] 무조건 사용하는게 맞는걸까? 🤔
You don’t (always) need [weak self]