Chapters

Hide chapters

SwiftUI Cookbook

Live Edition · iOS 16.4 · Swift 5.8.1 · Xcode 14.3.1

Finding Memory Leaks Using Instruments
Written by Team Kodeco

Memory leaks can cause your app to consume more memory than necessary, leading to poor performance or even crashing. Detecting and resolving memory leaks is an essential part of app development. Xcode’s built-in tool, Instruments, is incredibly handy in tracking down these memory leaks.

Consider the following code example:

class LeakyClass {
  var handler: (() -> Void)?
  let value: Int

  init(value: Int) {
    self.value = value
    handler = {
      self.doSomething()
    }
  }

  func doSomething() {
    print("Doing something...")
  }

  deinit {
    print("LeakyClass deinitialized")
  }
}

struct ContentView: View {
  @State private var isDetailViewShowing = false

  var body: some View {
    VStack {
      Button("Go to Detail View") {
        isDetailViewShowing = true
      }
      .sheet(isPresented: $isDetailViewShowing, content: {
        DetailView(leakyClass: LeakyClass(value: Int.random(in: 0..<1000)))
      })
    }
  }
}

struct DetailView: View {
  let leakyClass: LeakyClass

  var body: some View {
    Text("Detail View: \(leakyClass.value)")
      .onDisappear {
        leakyClass.handler?()
      }
  }
}

Here’s what your preview should look like:

A view referencing a class with a memory leak in SwiftUI.
A view referencing a class with a memory leak in SwiftUI.

In this code, you have a LeakyClass with a closure handler that captures self strongly. Each time DetailView is presented, a new instance of LeakyClass is created, but none of these instances are deallocated when the view is dismissed. This creates a memory leak.

To locate this memory leak, you can use the Leaks Instrument in Xcode:

Choose the Leaks profiling template within Instruments.
Choose the Leaks profiling template within Instruments.

  1. Run your app in the simulator or on a device.
  2. In Xcode, choose Profile from the Product menu or press Command-I. This will launch Instruments.
  3. In the template selection dialog that appears, choose Leaks.
  4. Click the red Record button at the top left to start profiling your app.
  5. In the app, perform the actions that you suspect are causing memory leaks. In this case, open and close the DetailView multiple times. After a while here’s what the Instruments window will look like:

The Instruments window showing the Leaks instrument.
The Instruments window showing the Leaks instrument.

After you’ve completed the actions, click the Stop button in Instruments.

Now, Instruments will show you a graph of memory usage over time. If there’s a memory leak, you’ll see the memory usage increase each time you present the DetailView, but it won’t decrease when the view is dismissed. Below the graph, Instruments provides a list of leaked memory blocks, along with a stack trace of where the leaked memory was allocated. This can help you locate where in your code the leak is occurring.

In this case, Instruments would point you towards the handler closure in LeakyClass as the source of the memory leak. This is because self is being strongly captured in the closure, leading to a retain cycle that prevents instances of LeakyClass from being deallocated.

To fix this memory leak, you would need to break the retain cycle by changing the closure to capture self weakly:

init(value: Int) {
  self.value = value
  handler = { [weak self] in
    self?.doSomething()
  }
}

Now, if you profile your app with Instruments again, you’ll see that the memory usage decreases each time the DetailView is dismissed. This confirms that the memory leak has been resolved.

Instruments is a powerful tool for finding memory leaks and other performance issues in your app. Regularly profiling your app with Instruments can help you maintain efficient memory usage and prevent leaks from impacting your app’s performance!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.