← All posts

Tutorial: Finding a Memory Problem in Swift

A small diagnostic flow for investigating memory pressure before jumping into premature optimization.

SwiftPerformanceMemory

Step 1: Reproduce the pressure

Start with a repeatable action: open a screen, scroll a list, decode a payload, process images or repeat a navigation flow. If the problem cannot be reproduced, optimization turns into guessing. Use a real device when possible because simulator behavior can hide important costs.

Step 2: Look for ownership first

Before touching algorithms, check whether objects are released. Retain cycles in closures, long-lived tasks, image caches and notification observers can make memory grow even when the code looks innocent.

final class ProfileViewModel {
    private var task: Task<Void, Never>?

    func load() {
        task = Task { [weak self] in
            guard let self else { return }
            await self.fetchProfile()
        }
    }

    deinit {
        task?.cancel()
    }
}

Step 3: Measure allocations

Use Instruments to watch allocations while performing the same action several times. You are looking for patterns: repeated large allocations, data that never drops, image buffers that stay alive, or transformations that create many temporary values.

Step 4: Reduce data movement

In Swift, performance issues often come from moving too much data too often. Avoid decoding the same payload repeatedly, resizing images on the main thread, building giant intermediate arrays, or copying large values through layers that do not need ownership.

Step 5: Verify the user impact

The final question is not whether the code is clever. It is whether the screen scrolls smoothly, memory stabilizes, startup remains fast and the app survives real user sessions. Measure before and after, then keep the simplest fix that changes the user experience.