DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Memory Pressure & Resource Management (Survive the OS)

Most SwiftUI apps don’t crash from bugs.

They crash because the OS kills them.

Symptoms:

  • random terminations
  • image-heavy screens freezing
  • background tasks killed
  • “works on my phone”
  • sudden reloads when switching apps

This post shows how to design memory-aware SwiftUI architecture that:

  • adapts under pressure
  • releases resources safely
  • avoids OS termination
  • keeps UX stable

This is about survival, not just leaks.


🧠 The Core Principle

Memory is not infinite, and the OS is not forgiving.

Your app must adapt when memory is constrained.


🚨 1. Understand Memory Pressure Signals

iOS sends warnings before killing your app.

Listen:

final class MemoryMonitor {
    static let shared = MemoryMonitor()

    init() {
        NotificationCenter.default.addObserver(
            forName: UIApplication.didReceiveMemoryWarningNotification,
            object: nil,
            queue: .main
        ) { _ in
            self.handlePressure()
        }
    }

    func handlePressure() {
        // trigger cleanup
    }
}
Enter fullscreen mode Exit fullscreen mode

This should live in AppContainer.


🧱 2. Cache with Eviction

Never cache unbounded.

final class ImageCache {
    private let cache = NSCache<NSString, UIImage>()

    init() {
        cache.countLimit = 100
        cache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
    }
}
Enter fullscreen mode Exit fullscreen mode

NSCache automatically evicts under pressure.


🧬 3. Release Heavy Resources on Background

func sceneDidEnterBackground() {
    imageCache.clear()
    videoManager.release()
    temporaryStore.clear()
}
Enter fullscreen mode Exit fullscreen mode

Background = prepare for kill.


🧠 4. Lazy Loading Is Mandatory

Bad:

let images = loadAllImages()
Enter fullscreen mode Exit fullscreen mode

Good:

LazyVStack {
    ForEach(items) { item in
        Row(item)
    }
}
Enter fullscreen mode Exit fullscreen mode

Only render what’s visible.


🧭 5. Downsample Images

Never load full-resolution images for UI.

Use downsampling:

CGImageSourceCreateThumbnailAtIndex(...)
Enter fullscreen mode Exit fullscreen mode

Even 12MP images should render as ~1MP for the screen.


🧪 6. Detect Memory Spikes

Use Instruments → Allocations.

Watch for:

  • scroll spikes
  • repeated image allocations
  • task churn
  • video buffers
  • unbounded arrays

Memory growth should flatten after navigation.


🧠 7. Background Tasks Must Be Cancelable

let task = Task { await loadHeavyData() }

task.cancel()
Enter fullscreen mode Exit fullscreen mode

Never let work continue when UI disappears.


⚠️ 8. Avoid Retaining ViewModels Forever

Bad:

@StateObject var vm = SharedVM.shared
Enter fullscreen mode Exit fullscreen mode

This prevents cleanup.

Good:

  • feature-scoped ownership
  • release on exit

❌ 9. Common Memory Anti-Patterns

Avoid:

  • storing images in AppState
  • caching without limits
  • holding onto large arrays
  • loading all data eagerly
  • not responding to memory warnings
  • ignoring background transitions

These cause OS kills.


🧠 Mental Model

Think:

Memory rises
 → Pressure warning
   → Cleanup
     → Release
       → Survive
Enter fullscreen mode Exit fullscreen mode

Not:

“It will probably be fine.”


🚀 Final Thoughts

Memory pressure is not an edge case.
It is the default on real devices.

Apps that adapt:

  • stay alive
  • feel fast
  • handle large data
  • survive multitasking

Apps that don’t:

  • get killed
  • lose user trust
  • feel unstable

SwiftUI doesn’t manage memory for you.
Architecture does.

Top comments (0)