SwiftMidMCQ

What are the most common causes of memory leaks in iOS applications?

Test your knowledge:

Explanation & Code

Answer: The most common causes of memory leaks in iOS are strong reference cycles between classes, closure capture retention, un-invalidated Timers, retained Combine subscriptions, and unmanaged Core Foundation allocations.

A memory leak occurs when heap-allocated memory is no longer needed by the application but cannot be freed by ARC because its reference count remains greater than zero.

  1. Retain Cycles in Class Hierarchies: Two objects holding strong references to each other (e.g. parent coordinator holding child view model while child holds strong parent reference).
  2. Closure Self-Capture: Stored closures or async callbacks capturing self strongly without [weak self].
  3. Non-Weak Delegates: Delegate protocols declared without : AnyObject and delegate properties declared without weak.
  4. Target-Action Timers & CADisplayLink: Timer.scheduledTimer holding a strong reference to its target object in the RunLoop.
  5. Combine Subscriptions: Storing an AnyCancellable set on self while the .sink closure captures self strongly.
  6. NotificationCenter Observers: Block-based observers added with addObserver(forName:...) whose token is retained by self while the block strongly retains self.
  7. Singletons and Global Managers: Registering closures or listeners to singleton instances without weak capture.
  8. Core Foundation / C-APIs: Failing to balance CFRetain / passRetained with CFRelease / takeRetainedValue.

Code Example:

// Common Leak Scenario 1: Retained Delegate
protocol NetworkServiceDelegate: AnyObject { // Must be AnyObject!
    func dataDidUpdate()
}

class NetworkService {
    weak var delegate: NetworkServiceDelegate? // weak avoids retain cycle
}

// Common Leak Scenario 2: Stored Closure Retain Cycle
class ProfileViewModel {
    var onAvatarLoaded: (() -> Void)?
    var avatarImage: String = "default.png"
    
    func setupBindings() {
        // ❌ Leaks if self holds onAvatarLoaded and onAvatarLoaded holds self
        // onAvatarLoaded = { self.avatarImage = "new.png" }
        
        // ✅ Safe with [weak self]
        onAvatarLoaded = { [weak self] in
            self?.avatarImage = "new.png"
        }
    }
}

Key Points:

  • A single leaked ViewController retains its entire subview hierarchy, image buffers, and child view models
  • Memory leaks lead to memory pressure warnings, background termination, and Jetsam OOM (Out Of Memory) crashes
  • Always check object ownership: parents own children strongly; children reference parents weakly

Rate your understanding:

Ready to practice more Swift?

Test yourself with our interactive quiz mode or browse all curated questions for this topic.