[alert color=”yellow”]Before we begin. I’m starting this year off with open ears. Please take a moment to provide feedback via this short survey. It’ll help me help you.[/alert]
If you have ever dealt with a collection view you can already sense the value of this post. Speed can quickly become an issue if you’re not paying attention. The result is that your customers will let you know in a heartbeat–so will you. You immediately know when a scrollview is not as fast as other apps on your device. Table views are one of the first things every junior iOS developer uses. Feeling lost can happen quickly. This post will dive into a few things that you can look for.
The Turtle & Hare Problem
Table views are an interface object that many apps take advantage of to display structured data. They’re trivial to put in place, which makes them a devious hazard.
The designer is not thinking about performance issues at design time. That designer could even be yourself. Soon you could be dealing with a photo app that needs to show a lot of information within a cell. What may start as a blazingly fast hare can turn into a noticeable turtle. You want your table view to feel smooth—like butter. Those using your app will immediately notice when it isn’t.
Speed up Your Table Views
We’re going to explore these tips by walking through a practical example of a poorly implemented table view.
Often you’ll find a photo app doing several things on an image feed view:
- Downloading Images (the main content image + user profile images)
- Updating timestamps
- Laying out comments
- Calculating dynamic cell heights
In this example we’re going to focus on a couple of those.
I encourage you to clone the repository to feel how bad things are at the onset. Jump into XMCFeedTableViewCell and walk through each improvement and feel the performance. It’s important to note that if you’re running an iPhone 6+ an improvement may not feel nearly as good. Don’t forget to consider the experience running on a much older phone.
[alert color=”yellow”]github.com project[/alert]
Tip #1 Learn How to Measure Speed
I could write an entire post about instruments. Here I will give you a quick overview because it’s helps quite a bit.
If you don’t have experience with Instruments I urge you to take some time over the weekend to explore a few of them. When you’re trying to measure memory or time usage they will save your tail. While you may not be concerned with performance when you’re just beginning an app you’ll inevitably run into issues down the road when code starts to become a bit of a mess. Refactoring becomes essential. To refactor properly you’ll need to focus your efforts by analyzing performance.
So, if it’s the weekend explore:
- Open your project and click Product > Profile
- From there select Custom
- Find the add button (+) and add the instruments: Allocations, Time Profile, Leaks
- Observe your own application & how it performs
For this example, our concern is with speed (but memory is a big issue as well). Which tool do we need? If you said Time Profile you’re right. Let’s open that open it up and observe our app in motion.
Here you see a profile of our app. What you’re seeing is me opening the app and scrolling the table up and down as fast as I can. This simulates a decent “worst case scenario” that we can take action on.
This area is where I begin scrolling the app. We only want to know the time consuming calls during this period.
Now you can start to investigate the code in question. Double click on any of those rows (preferably the rows near the top, where the most time is being consumed).
It’s important to point out that the options selected under Call Tree do not set for you when instruments loads. You’ll need to flip these on yourself.
Tip #2 Avoid Blocking the Main Thread
In the example you’ll notice that the first photo method is blocking the main thread while data is downloaded and converted to an image object. You should always avoid blocking the main thread, but this is especially important with interface object drawing in a collection. Network calls? Keep them in the background (async) and cache responses that come back. You want to avoid re-processing anything. Consider the moment that cells are drawn as dummy time. Your cells should only be displaying data that has already been saved on the device. This will make your life much easier.
Tip #3 Reuse Cells
If you have spent any time in iOS I apologize. But this tip targets those who are new to iOS. You should be using the dequeueReusableCellWithIdentifier to fetch table or collection view cells. If you’re not then you’re wasting an insane amount of time and data.
Tip #4 Cache Downloaded Images
This can easily be the most important tip you’ll read here. If you’re not caching images then you’re experiencing a huge penalty.
If you reuse images locally then take advantage of the UIImage method imageNamed:.
Save time and resources by serving your images in a JPG format. If you’re getting your image from a server you have the luxury of sending the exact image that’s needed. PNG files can be a huge hit in the memory department. If you’re curious jump into the example and change JPG to PNG (the directory and the file name) to download a set of PNG images.
Tip #5 Building Attributed Labels is Expensive
Given all the things you can do with an attributed label, the cost is high. Do what you can to avoid doing doing this outright. Ask yourself i you need attributed text. If so, cache what you can cache.
Tip #6 Cell Height Calculation
If your table has complicated dynamic height needs then cache the calculated heights. Considering how often calculations are made (especially with collection views) you want these to be readily available.
Tip #7 NSDateFormatter Pain
Like attributed text, date formatters can result in a large penalty up front if you’re constantly initializing them. Ideally your web service will provide human readable text on the fly (much easier to calculate at that end). If not then you should create a singleton NSDateFormatter object that you can use. NSDateFormatters used to not be thread safe but that is no longer the case with iOS 7 and later. Thanks to quellish for reminding me of this.
Tip #8 Transparency
If you can avoid it your objects should be opaque (not transparent; can’t see through it). When you have transparent images the system must work much harder to redraw these items. You can actually see negatively affected areas in the simulator by clicking Debug > Color Blended Areas
Notice the RED? That means those areas are transparent. This is extremely time consuming when you’re dealing with something like a collection view. Ideally, you want to see this screen all green. That may not be feasible for your design so strive to cut the amount of red you see. In the example you can see that the label stretches to the trailing side of the view, which can be cleaned up.
Tip #9 Don’t Bloat Xibs (use Storyboards if you can)
Be careful if you’re using Xibs. When you load a Xib the entire contents are loaded into memory (images! hidden views!). This will not happen with storyboards since they only instantiate what is needed at the time.
There are valuable scenarios where creating xibs make sense. Maybe you’re using a 3rd party library and they decided to write collection interface with code. If you want to use an xib to create a prototype cell you can do that with a xib. Just be cautious about overloading them.
Tip #10 Drop to CoreGraphics
I rarely need to do this, but in the event you need the power you can. Drop down to CoreGraphics and write your UI code in the drawRect function of a view.
Who doesn’t love a good challenge? Let’s keep it simple here. Spend some time doing these two things this weekend:
- Learn how to use instruments (time profiler, allocations)
- Verify you learned how to use instruments by experimenting with image caching (turn it off, turn it on, observe)
[alert color=”yellow”]github.com project[/alert]
Question & Answer
No questions yet! Leave a comment or write me at [email protected]
Interface performance is important on iOS. This is non-negotiable. If you don’t take the time to solidify your user experience on this device people will run far, far away. You just can’t have an interface that is constantly stuttering while I try to review content.
What is your top tip for handling performance? I’d love to hear about them. Please feel free to share below.