Updated: Nov 20, 2018
People hate when apps crash, or even when they slow down or freeze for a few seconds. If we take a look on a survey made by Dimensional Research, we’ll find out that 61% of users can wait maximum 4 seconds until the app is open, while 49% of users wait only 2 seconds or less. If there are any troubles with the mobile app, 51% of users will uninstall it.
User expectations from iOS apps are high. The perfect app is:
You find yourself working hard on a brand-new iOS application to fulfil all the client’s requirements. It works great while you test it in your local environment.
But when you release it to the App Store, the story begins. The download rates of your app are high, but the retention rate decreases day by day.
To understand what’s wrong, you launch Xcode and Instruments as your favorite debugging tools and you start hunting down performance issues and bugs from the growing backlog of tasks.
I won't be able to tell how to solve each of these issues, I'd be happy to share some of the more common problems I've encountered throughout years of my iOS development experience.
Let’s start with the very first problem that comes to my mind when I think of iOS development.
Choose a Proper Thread
iOS is a multithreaded environment, which means that iOS applications can be implemented to run using main thread (aka UI thread) and additional background threads. Multiple articles have been written to explain what UI and background threads are and how to choose what operations to run in which thread. In simple terms, all user interface manipulations should be happening on the main thread and all costly operations should be implemented to run within a background thread.
Sounds simple? Still many of us tend to make mistakes such as attaching or removing subviews within a callback from a networking call that's happening in a background thread, or parse that huge JSON response on the main thread and thus stalling a UI which tend to be a most annoying thing for our users.
I highly recommend spend time validating the proper threads usage within your app. As an example, a place to start could be validating that all content scrolling within your app is smooth.
Inconsistent Data Sources
OK, enough of threading. Let's touch on two of the most common UI components used in development: UITableView and UICollectionView. Both of them get data through a corresponding datasource object. Pretty basic stuff.. however you really need to make sure any data updates have to go first through your backing model and only then have your UI react to data changes. The reverse chain of actions will most likely lead to unexpected crashes.. and I've seen a lot of them.
Image Handling In Your App
Let's move on to something different but yet still related to performance handling of your app. Showing high res images in the app is so common these days that chances are as a developer you already came across a process of fetching images from the server in order to show it to your users.
Now, scaling large high-res images dynamically in your app to fit the UIImageView size is taking device resources and thus slowing down app performance as users see it.
The solution could be to optimize images on the server side before they're received on the client side. That is if you have access to the server API. But what if you don't? Most likely the answer is to process images in the background as have been mentioned above.
Same goes for working with JSON data fetched from the server. You need to make sure not to overload main thread with constant deserialization and object creation.
Use of Database for App’s Data Persistence
Here’s another place to look at when optimizing an app. Whether you’re using Apple’s Core Data or a third party library such as Realm, it’s important to take a deep dive into essentials of it to avoid painful debugging down the road. Were you able to separate the data model access into its own layer? Great! Then it will be mostly straightforward to optimize those data lookups and updates. Just make sure all data is consistent across the wire (e.g. between your server and the app).
Here at Beantown Mobile our database of choice is Realm. It has helped us a lot avoiding writing a complicated synchronization logic. Stay tuned for an in-depth article on how we use this 3rd party persistence library in the apps we create.
Autolayout is widely adopted and as long as you don’t carry any conflicting constraints you should be fine, right? Not exactly, overuse of constraints can lead to performance issues. Also, avoid using any 3rd party libraries to solve auto layout - you will only make it harder for yourself to investigate any issues related to constraints management when the time comes. My advice here is to take another look at the UIStackView which will help optimize UIVIews distribution.
With that said, I suggest you keep in mind auto layout for a potential violator on a road to optimize and to scale your application.
What are retain cycles in iOS environment and why do you care? Yes, we do have Automatic Reference Counting at our disposal, however having a strong reference between objects pointing to each other will stay in memory and ARC won’t be able to help. It’s not easy to spot these issues but you have tools to help and it’s very important to periodically check the app for any of these memory leaks.
There’re well-known solutions to this - you could use “Structs” as opposed to “Classes” whenever possible (remember, that “struct” entities are passed by value and classes a passed by reference).
Also, avoid “Strong” relationship between your objects that reference each other. “Weak” and “Unowned” reference types are here to help you!
Sounds straightforward? But finding and addressing this issues can be very time-consuming, so I’d suggest keeping these best practices in mind as an application is being architected and implemented. It will save you a lot of time down the road as you optimize and scale your app.
Don't take this the wrong way - optimizing, fixing and overall scaling your app is a good problem to have. It means your app is needed and being used by real users out there. If you help yourself in the beginning by following through on a good app architecture, established software design patterns and avoiding over-engineering your code, then this process of debugging and scaling might, in fact, be a fun exercise. Carefully going through this will lead to your application getting high ratings and your client coming back to you with more interesting feature requests.