Animation killed the CPU star

Working on an app for a client recently threw up an interesting issue with CPU performance. Basically we were maxing out the CPU, even on the most plain of view controllers displayed. Finding out why lead us to discover some interesting side effects to using animations.

Here's an example app that will demonstrate the problem.

The view in the middle of the first view controller will rotate. The second view controller in the navigation stack does nothing whatsoever. 

Here's the CPU performance with only the first view controller displayed. Note that the app is running directly on an iPhone, not in the simulator.

The rotation animation is optimised so well, I guess running directly in the GPU, that the CPU of the phone (iPhone 6S, 9.3) is at 0% the whole time. So what happens when I tap the "Show" button to segue to the next view controller? 

It's this:

Holy ridiculous battery drain, Batman. Once the second view controller is displayed the CPU ramps up to 100% immediately, and just stays there until the view controller is dismissed. When the second view controller is dismissed the CPU usage goes right back down to zero.

Running the app through Instruments shows this CPU profile:

which is what we’ve seen above. With the time profiler we can see where we’re spending all that time doing nefarious things to the phone’s CPU. From the trace in the timer we can see that we’re spending almost all of the time either in _CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE or _CFRunLoopDoObservers. Lets take a look at the latter first:

Ok, so there’s a whole load of CoreAnimation stuff going on. Which doesn’t make sense because the second view controller isn’t doing any animation.

Let’s have a look at the other trace, which is taking 52% of our time:

Oh, UIViewAnimation is everywhere, as is my rotation behaviour. That’s coming from the first view controller, not the second one. It seems that if you cover an animation so that it is no longer visible on screen, the GPU no longer cares for it, which makes sense, but the animation continues. When that happens the only place to keep the animation going is on the CPU. 

In my view controller I'm going to switch off animations when the view controller isn't being displayed

Aside: You might wonder what is going on here, my rotation behaviour listens for notifications on when to start and stop, so I keep all the responsibility for the implementation of the animation out of the view controller while allowing it to keep the responsibility for the control of the animation, the way it should be.

What does this look like now for our CPU? Here's the performance of the app with the 'stop' notification in place.

There is a slight jump in the CPU as the animation completes when the view controller disappears, but nothing too significant. So, golden rule, if you’re creating animations that may execute while offscreen, it would be best for your app’s performance to make sure they only animate while onscreen.