iosmemory-leakstoday-extensionios8-today-widgetios10-today-widget

Today Widget uses more memory each time it refreshes, then eventually crashes


I’m working on a Today Widget, and running into memory issues.

When I run the widget and monitor memory usage Xcode, the widget uses about 15MB when it first starts up. Then, when I swipe away from the widget screen and back, it goes up to about 16MB.

Every time I swipe away and back it memory usage goes up by about 0.5–1.5MB. As I do it more I get memory warnings (didReceiveMemoryWarning() is called) and eventually, with more swiping, the widget crashes.

All these symptoms are happening while testing on an iPhone X. On the simulator, the widget starts off using around 50 megabytes, which seems kind of odd, but it has the same behavior where memory usage goes up each time I swipe away and swipe back.

I've tried analyzing this with Instruments, but I'm only able to get Instruments to show what happens at the beginning (when I first launch the Widget) and it doesn't keep running as I swipe away and back.

Through the process of elimination (commenting out the actual functionality of my widget) the issue still happens when just the UI code remains. This makes me think it’s a problem with my UI approach.

I’ve built Today Widgets in the past, but always used Interface Builder. This time I decided to build the interface programmatically instead. I don’t see this same behavior of increasing memory usage with each refresh when I look at other Today Widgets I’ve built with Interface Builder.

First, I set up all my UI elements as private lazy variables, like this:

private lazy var mainStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.distribution = .fillEqually
    stackView.translatesAutoresizingMaskIntoConstraints = false
    return stackView
}()

Then, in viewDidLoad(), I add my views to the TodayViewController's view with some constraints, like this:

view.addSubview(mainStackView)

let stackViewLeadingConstraint = mainStackView.leadingAnchor.constraintEqualToSystemSpacingAfter(view.leadingAnchor, multiplier: 1)
let stackViewTopConstraint = mainStackView.topAnchor.constraintEqualToSystemSpacingBelow(view.topAnchor, multiplier: 1)
let stackViewTrailingConstraint = view.trailingAnchor.constraintEqualToSystemSpacingAfter(mainStackView.trailingAnchor, multiplier: 1)
let stackViewBottomConstraint = view.bottomAnchor.constraintEqualToSystemSpacingBelow(mainStackView.bottomAnchor, multiplier: 1)

view.addConstraints([stackViewLeadingConstraint, stackViewTopConstraint, stackViewTrailingConstraint, stackViewBottomConstraint])

Since I'm new to building UI programmatically, is there anything with this approach that seems blatantly incorrect and looks like it could cause a memory leak?

I've even tried commenting out all of my code and running it with a basic blank UIViewController, where the view lifecycle functions don't even do anything, and I still experience the memory leak. That makes me think there's something I should be doing when I build the interface programmatically that I'm not doing.

It seems like something's not being released and being duplicated in memory each time the Today Widget appears. I'd appreciate any suggestions for how I could find what's not being released and force the system to release it. Thanks!


SOLVED

Thanks to the troubleshooting tip provided by Christopher Pickslay, I was able to track down the issue. It turned out to be my fault. To troubleshoot a previous unrelated issue, I had turned on the Zombie Objects in the scheme, and I still had it enabled. Once I turned that off the issue went away. This is the setting I'm talking about: Zombie Objects checkbox in Xcode


Solution

  • I don't see any problem with how you're building the UI (from what you've shared so far). Instead of Instruments, try using the Memory Graph Debugger to find your leak.

    Memory Graph Debugger

    It will pause the debugger and you can use the top bar to browse through all the alocations and what points to each instance. Open the memory graph debugger, look at your graph, then un-pause and scroll the extension off and back on screen a few times, and open the memory graph debugger again. That should give you a better idea what's leaking and what's holding on to it.