Browse Questions

Answer: Combine subscriptions and NotificationCenter block observers cause leaks when the observation token is stored on self while the callback closure captures self strongly.

In Combine, storing a subscription in var cancellables = Set<AnyCancellable>() on self while the pipeline's .sink closure references self creates a circular reference: self → cancellables → AnyCancellable → sink closure → self.

In NotificationCenter, addObserver(forName:object:queue:using:) returns an opaque observer token. If self retains the token and the observer closure strongly captures self, neither is ever deallocated unless removeObserver is called.

Code Example:

import Combine
import Foundation

class SearchViewModel {
    @Published var query: String = ""
    private var cancellables = Set<AnyCancellable>()
    private var observerToken: NSObjectProtocol?
    
    init() {
        // ✅ Combine: Break cycle using [weak self] in sink
        $query
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .sink { [weak self] searchQuery in
                guard let self else { return }
                self.performSearch(query: searchQuery)
            }
            .store(in: &cancellables)
        
        // ✅ NotificationCenter: Break cycle using [weak self] & cleanup token
        observerToken = NotificationCenter.default.addObserver(
            forName: UIApplication.didEnterBackgroundNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.saveState()
        }
    }
    
    deinit {
        if let token = observerToken {
            NotificationCenter.default.removeObserver(token)
        }
    }
    
    private func performSearch(query: String) {}
    private func saveState() {}
}

Key Points:

  • Modern Swift apps should use Combine or NotificationCenter.default.notifications(named:) async sequences with [weak self]
  • AnyCancellable automatically cancels its upstream publisher when deallocated, provided there is no retain cycle preventing its deallocation