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:
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:
- Run your app in the simulator or on a device.
- In Xcode, choose Profile from the Product menu or press Command-I. This will launch Instruments.
- In the template selection dialog that appears, choose Leaks.
- Click the red Record button at the top left to start profiling your app.
- 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:
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!