Question

I'm seeing some performance degradation in my application after some time and I'm trying to figure out what's going exactly.

I have a complex view controller (VC1) which contains scroll view, few table views inside, some custom cells with horizontal scrolling and custom drawing etc.

After several (around 10) refreshes of all these objects (reloading tables, reposition subviews etc) when I try to call presentViewController to push another view controller above VC1 I can see about 2 seconds delay between viewWillDisappear and viewDidDissapear

I tried to profile the app to see if there are memory leaks but couldn't find any. Memory usage grows when view refreshes and switches between different modes, but then it become more or less stable in around 30m.

Works fine in Simulator, but visible slower on iPhone5. And this slowness is visible only when I try to switch from that view controller.

I ran a profiler and recorded where these 2 seconds are spent. Here is link to trace file: https://dl.dropboxusercontent.com/u/6402890/trace.trace.zip

Majority of the time spent by UIKit doing layout as I can see.

What can I do to optimize it? Is there way to take may be a snapshot of a view and use it for "leaving view" animation and restore view hierarchy when we're coming back?

UPDATE: Adding screenshot for the profiler (click for full resolution):

screenshot

UPDATE2:

After analyzing output from recursiveDescription I can see the following:

  • In the easiest case I have ~200 lines in the output. And performance is ok.
  • When I switch to more complex scenario hierarchy of views growth to ~500 lines, but still performs ok.
  • After multiple refreshes this number goes to ~2000 and this is where it become slow. Analyzing output with 2000 views I can see that ~1500 of them belong to hidden cells that are not even displayed in this mode anymore. When I'm refreshing table views cell types change too, and I'm utilizing different cells, but why the cells that are not used anymore are still being in subviews of table views?

Any recommendations?

Was it helpful?

Solution 2

From the Instruments Time Profiler output, you can see that NSISEngine is eating up a ton of CPU. That class is responsible for doing the Auto Layout constraint evaluation and layout calculation.

So it looks like you are using Auto Layout, at least for some of the views.

Are you by any chance removing and re-adding constraints anywhere at runtime? I've seen this exact problem caused by that (can explain more if this is relevant).

If you aren't removing constraints, it sounds like you might have a fairly complex view hierarchy, and if you're using Auto Layout throughout, it's likely that you have a lot of constraints. As you may know, Auto Layout degrades in performance pretty quickly above a certain point due to super-linear time complexity of solving constraints. Check the output of po [[UIWindow keyWindow] recursiveDescription] from the debugger to see what your view hierarchy looks like.

I'm not sure what your view controller transition looks like, but you could try removing the disappearing view controller's view from its superview before doing the present. That should prevent it from doing layout calculations as it transitions. If that solves the performance issue, you could quickly snapshot the view hierarchy and then replace it with a single new UIImageView of the snapshot to display during the transition animation.

(One final thing: are any of your table views using Auto Layout in their cells? do any of these table views have more than ~20 cells?)

OTHER TIPS

From your stack, I suspect you've added a large number of views you didn't mean to add. Since it's related to reloads, I would check your reload logic and make sure it doesn't re-add all the views in your hierarchy without removing the previous views. You can write a quick debug routine use -recursiveDescription to recursively walk the -subviews of each view and print them out to see what's in the hierarchy.

It's possible that your issue is in the layer hierarchy rather than the view hierarchy, but the symptoms you describe make me think views.


EDIT: From your update, you probably have one of two things going on. Most likely, if these are actual UITableViewCells that shouldn't even exist anymore, then you have a retain loop somewhere. Alternately, your cellForRowAtIndexPath: may be incorrect and may be adding new views to an existing cell when it should just be reconfiguring the cell.

In either case, though, 200 views seems a lot of views for a "best case." You may be overusing views in places that you should be doing custom drawing. If the performance is ok, then… ok, but I'd test carefully on your slowest supported devices.

When presenting another controller your original controller has to be animated out of the window, which causes the view's frame to change and probably triggers all layoutSubviews methods and your manual adjustments.

You could try to avoid this by deactivating autoresizesSubviews in viewWillDisappear.

It is not very clean, but then probably all the calculations you are making may not be as well!

Try to optimize them:

  • Don't call layoutSubviews directly and call setNeedsLayout only if really needed.
  • Try to replace your manual resizing code with autoresizingMask or autoLayout.
  • Adjust views lazily and only if they are visible and if their size and not origin really has changed.
  • Don't reload your tables entirely but try to change only individual rows.
  • Try to use only a single table view.
  • Make sure to reuse cells.

The problem may have something to do with that you are placing UITableView instances inside a UIScrollView. That is explicitly prohibited by Apple's documentation for UIWebView (surprise!):

Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behaviour can result because touch events for the two objects can be mixed up and wrongly handled.

I suspect that may also mess up table view cell reuse mechanism. Anyway, I'd also recommend to check if you are not 'leaking' any views at all. Keep in mind that even invisible views participate in layout if they are in the view hierarchy.

Edit: in response to Update 2

It's evident that cell reuse mechanism is not functioning properly. Try to make sure you are using correct instances of UITableView when dequeueing table view cells from reuse queue (check your data sources).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top