Swift의 closure에서 weak self를 사용해야 하는 경우

작성일 :

Swift의 클로저에서 'weak self'를 사용해야 하는 경우

Swift에서 클로저는 매우 강력한 기능을 제공하지만, 잘못 사용하면 메모리 누수(memory leak)와 같은 문제를 일으킬 수 있습니다. 특히 클로저가 객체의 참조를 강하게 유지할 때, 강한 참조 순환(strong reference cycle)이 발생할 수 있습니다. 이로 인해 메모리 관리 문제가 발생합니다. 이를 피하기 위해 'weak self' 키워드를 사용하게 됩니다. 이 글에서는 'weak self'를 사용해야 하는 경우와 그 이유에 대해 설명하겠습니다.

클로저와 강한 참조 순환

Swift에서 클로저는 참조 타입(reference type)이며, 객체의 프로퍼티로 사용될 때 그 객체를 강하게 참조할 수 있습니다. 예를 들어, 객체 내에서 클로저를 프로퍼티로 갖고, 그 클로저가 다시 그 객체를 참조한다면 강한 참조 순환이 발생할 수 있습니다. 아래 코드 예제를 통해 설명합니다:

swift
class MyClass {
    var closure: (() -> Void)?
    
    func doSomething() {
        closure = {
            print("Doing something in MyClass")
        }
    }
}

let instance = MyClass()
instance.doSomething()
instance.closure?()

위 코드에서 instanceMyClass의 인스턴스로, doSomething 메서드를 호출하면 closure 프로퍼티가 클로저를 할당받습니다. 이 클로저는 MyClass 인스턴스를 강하게 참조하여 메모리에서 해제되지 않는 문제가 발생할 수 있습니다.

weak self 키워드로 해결

이러한 강한 참조 순환을 피하기 위해 weak self 키워드를 사용할 수 있습니다. weak self를 사용하면 클로저 내부에서 객체를 약한 참조(weak reference)로 유지할 수 있습니다. 약한 참조는 객체가 메모리에서 해제되는 것을 막지 않습니다. 따라서 클로저 내에서 객체가 강하게 참조되지 않습니다.

다음 예제를 통해 weak self 사용을 설명하겠습니다:

swift
class MyClass {
    var closure: (() -> Void)?
    
    func doSomething() {
        closure = { [weak self] in
            guard let self = self else { return }
            print("Doing something in MyClass")
        }
    }
}

let instance = MyClass()
instance.doSomething()
instance.closure?()

이 예제에서 클로저는 [weak self] 캡처 리스트를 사용하여 self를 약하게 참조합니다. 클로저 실행 시점에 self가 존재하는지 확인하기 위해 guard let self = self else { return } 구문을 사용합니다. 이를 통해 강한 참조 순환을 방지할 수 있습니다.

언제 weak self를 사용해야 할까?

다음은 weak self를 사용해야 하는 경우입니다:

  1. 비동기 작업: 네트워크 요청, 타이머, 비동기 클로저 등의 작업에서 self를 참조할 때 weak self를 사용하여 강한 참조 순환을 방지합니다.
  2. 클로저 속 콜백: 콜백이 객체를 소유하거나 객체가 콜백을 소유할 때 약한 참조를 사용하여 두 객체가 서로를 강하게 참조하지 않도록 합니다.
  3. 중간 상태 유지: 클로저 내에서 객체의 중간 상태를 유지하는 경우에 weak self를 사용하여 객체가 메모리에 계속 남아있지 않도록 합니다.

아래는 네트워크 요청 예제입니다:

swift
class NetworkManager {
    var completionHandler: (() -> Void)?
    
    func fetchData() {
        completionHandler = { [weak self] in
            guard let self = self else { return }
            // 데이터 처리 코드
            print("Fetched data")
        }
    }
}

let networkManager = NetworkManager()
networkManager.fetchData()
networkManager.completionHandler?()

네트워크 요청이 완료되기 전 NetworkManager 인스턴스가 해제되더라도 클로저 내에서의 self 참조가 약한 참조인 경우 메모리 누수를 방지할 수 있습니다.

결론

Swift에서 클로저를 사용할 때 weak self를 사용하면 강한 참조 순환을 방지하고 메모리 관리 문제를 해결할 수 있습니다. weak self를 사용해야 하는 경우와 그 사용 방법을 이해하면 앱의 메모리 사용 효율성을 높이고 예기치 않은 크래시를 방지할 수 있습니다. 위에서 설명한 내용을 기반으로 weak self를 올바르게 사용하여 안정적인 코드를 작성할 수 있도록 노력합시다.