I’ve been finding a lot of retain cycles in our app's memory graph. I know I should use '[weak self]', but I’m not sure when it’s strictly necessary and when it’s overkill. Does every closure in a ViewModel need a weak reference to self? I want to understand the underlying mechanics of Strong Reference Cycles so I can write cleaner, leak-free Swift code.
3 answers
Memory management in Swift is handled by Automatic Reference Counting (ARC). You only need [weak self] if the closure is "escaping" (stored for later use) AND if self also holds a strong reference to that closure. A classic example is a network manager closure stored in a property. If you don't use weak, the ViewModel owns the closure, and the closure owns the ViewModel—neither can ever be deallocated. In 2024, I always recommend using the Memory Graph Debugger in Xcode every Friday. It’s a 10-minute check that has saved us from dozens of hidden leaks that weren't obvious during code reviews.
What about 'unowned self'? I’ve heard it’s faster than 'weak' because it doesn't involve optional unwrapping, but is it too dangerous for production apps?
One tip: non-escaping closures (like those in 'map' or 'filter') never need '[weak self]' because they are executed immediately and don't create long-term cycles.
That’s a common misconception cleared up right there. I see a lot of developers cluttering their code with 'weak self' inside simple 'forEach' loops where it's not needed.
Jeffrey, 'unowned' is like playing with fire. It assumes 'self' will always exist when the closure runs. If the object is deallocated and the closure is called, your app will crash instantly. In 99% of cases, the performance gain is so microscopic that it’s not worth the risk. Stick to 'weak self' and use the guard let self = self else { return } pattern. It’s the industry standard for a reason: it’s safe, predictable, and much easier for teammates to reason about.