아래 세개의 버전 중 순환참조가 발생하는 녀석은 뭐가 있을까?

// Version A
class viewControllerA: UIViewController {

	var workItem: DispatchWorkItme?

	override func viewDidLoad() {
		let workItem = DispatchWorkItem { 
			UIView.animate(withDuration: 1.0) {
				self.view.backgroundColor = .red
			}
		}
		self.workItem = workItem
	}
}

// Version B
class viewControllerA: UIViewController {

	var workItem: DispatchWorkItme?

	override func viewDidLoad() {
		let view = self.view
		let workItem = DispatchWorkItem { 
			UIView.animate(withDuration: 1.0) { [weak self] in
				view?.backgroundColor = .red
			}
		}
		self.workItem = workItem
	}
}

// Version C
class viewControllerA: UIViewController {

	var workItem: DispatchWorkItme?

	override func viewDidLoad() {
		let workItem = DispatchWorkItem { 
			UIView.animate(withDuration: 1.0) { [weak self] in
				self?.view.backgroundColor = .red
			}
		}
		self.workItem = workItem
	}
}

정답은 세 버전 모두 순환참조가 일어난다는 것이다.

무턱대고 weak self를 남발해도 제대로 알지 못하면 메모리릭을 일으킬 수 있다는 것이다.

개발을 하다보면 복잡한 클로저들을 만들 때가 있다

retain cycle을 피하기 위해 [weak self] 를 써주어야 하는데 이러한 상황에서 모든 클로저에 weak self를 다 써줘야 할까?

class ViewControllerB: UIViewController {
    
    var workItem: DispatchWorkItem?
    
    override func viewDidLoad() {
        let view = self.view
        let workItem = DispatchWorkItem {
            UIView.animate(withDuration: 1.0) { [weak self] in // this leaks
                view?.backgroundColor = .red
            }
        }
        self.workItem = workItem
    }
}

처음에 UIview.animate에 weak self를 집어넣어주고 view 프로퍼티에 접근을 위해 weak self를 넣었지만 이것은 순환참조를 일으키니 DispatchWirkItem에 넣어주어야 한다.

workItem은 self.workItem = workItem이라는 강한참조가 있는데 클로저는 self에 대한 강한참조도 저장한다.

이 self에 대한 강한 참조는 중첨된 클로저 내부에서 weak self로 표현된 버전 B에서도 발생된다,.

이 경우 weak self를 추가하는 것만으로도 실수로 자기에 대한 강한 참조가 생성되어 순환참조가 발생된다.

Version B에서 self에 대한 강한참조가 발생되는데 animtate 클로저 안에 [weak self]를 넣어주어서 그런 것이다.

이를 해결하기 위해서는 어떻게 해야 하는가?

DispatchQueue 부분에 weak self를 옮겨 주어야 한다.

만약 [weak self]가 낮은 레벨로( 복잡한 클로저 안 더 깊숙히) 이동하게 되면 메모리릭을 경험할 것이다.