Hello, my name is Keonhong Lee and I’m in charge of development for Timeline on iOS. In this blog post, I’d like to talk about how we improved performance of Timeline on iOS, after it had become heavy, slow, and difficult to manage from the many added features and history over time.
First, some background on why we started on this path.
Timeline is a place where you and your friends can share all of your social activities. Theoretically, a post uploaded to Timeline can be seen in dozens of different ways. One single post may need to processed in different ways depending on the screen it’s being displayed on, or the situation it’s being read in.
Up until now, we’ve used our own guidelines when developing for Timeline instead of following the development guidelines set up by Apple.
As more features were added on to Timeline, our guidelines have caused several side effects to accumulate over time, as you can see below.
- From a user perspective
- “Timeline is unresponsive and slow.”
- There is noticeable choppiness when scrolling down the screen on Timeline.
- From a developer perspective
- Where and how do you want us to fix the slowdown?”
- As the creation process of a post and the flow of data were not clearly visible in the code, it was difficult for us to determine exactly what was causing the performance drops.
- “Why does it take so long to develop anything?”
- Trying to balance Apple’s development guidelines and our own guidelines led to an overall higher maintenance cost.
- “I change one screen, and another gets messed up.”
- The relationship between views were overlapping.
- Adding or editing views required knowledge of the entire architecture of Timeline, and everywhere that view was used.
- Where and how do you want us to fix the slowdown?”
With these problems presented in front of us, we started thinking of ways to improve the structure.
The previous Timeline feed list structure
In order to display a post on the user’s screen, the Timeline maps post data sent from the server and cells on the UITableView one-to-one.
- Each cell is comprised of several views, and each view is then further divided into several sub views
- Data makes a pass on every single subview in a cell, attempting to relay relevant data where it’s needed
- When the appropriate data is sent to every view, the information is displayed on the user’s screen
As you can see above, we had to follow the Timeline guidelines to relay data and manage views in order to manage all views on the Timeline in a uniform fashion.
Causes of non-optimal performance and high maintenance costs
Low reusability of each cell
Each cell is comprised of various different views. If we wanted to present data in a different way, we needed to create an entirely different type of cell just to make a slight change on the views. As cells couldn’t be reused, we were often creating new cells, which led to drops in performance.
High data transfer costs
Because of how various different views are under each cell, it can cause the UI to freeze up on the user-end whenever data makes a pass, attempting to relay the relevant data to the views. It was difficult to ascertain when a certain view receives a certain piece of data, leading to more time spent trying to find a solution.
Excessive number of tasks on UI Thread
The previous structure didn’t allow any processing of data beforehand, as it wasn’t possible to know what data each view required. As the data processing tasks required to draw the views on the user’s screen repeatedly occurred on the UI Thread, it led to a drop in UI performance.
Our own development guidelines for the Timeline
We suspect that the process of trying to integrate our own development guidelines with the guidelines provided by Apple resulted in higher maintenance costs, and ultimately led to poor performance on the final product.
Ideas for improvement
We gathered the following ideas to try and improve the structural flaws that we found.
By splitting cells as much as possible instead of displaying one post through one cell, we could increase the reusability of cells and in turn improve performance.
In the process of splitting the large and complex cells, we were able to find and correct unnecessary or flawed structures. As the size of the cells become smaller, it’s easier to find the required data for each cell.
Unified view model protocol
The Timeline has many different views of different types and purposes. Handling tasks for these views with a view controller will inevitably put too much load on the view controller.
So we defined a PostTableViewModel protocol with functions that are commonly required when creating cells for tableview.
Also, we made it possible to use data and draw a view as seen below.
- Developing individual view models that use a protocol that processes and stores post data.
- The view controller doesn’t access data or views directly, but instead only uses the protocol to access view models and draw views on the screen.
By using the methods above, we were able to implement all the logic required for drawing views inside the view model. This makes the overall structure much simpler as the view controller no longer needs to know which view it needs and you can focus on the structure of view models and views without being concerned about the relationships with other views.
Background thread optimization
Now the background thread can easily handle many tasks instead of the UI Thread.
We’ve further improved performance by using the background thread to process data required for views so that the data can be reused anytime they’re needed.
Low background thread priority
In the past, we set the background thread in charge of handling post data to have high priority in order to quickly show results to the user when they control the UI.
Giving high priority to the background thread can show results to the user more quickly, but it affected every other UI action.
Now when a user performs an action such as a scroll, it’s run as a background job and the thread priority is lowered, instead of showing the results right away. While this may show the results to the user slightly later, it will no longer affect other UI actions.
The new Timeline feed list structure
With the modified structure above, views are drawn on the screen in the following order.
- When the server sends post data, the post data is grouped into an array.
- The post data array is processed into a view model array.
- Tableview accesses the view model array instead of post data to create small cells, drawing the view using the view model’s data.
As view models implement the same protocol, tableview can access view models by only using the protocol, no matter what type of data.
The view model can determine which views should be used on the cell, and it can process the data required for the views in advance, and store it.
And as view models are divided into smaller cells, it’s easy to modify or replace the view as long as you’re aware of the relationships between the view model, cell, and view.
View model creation process
In the previous structure, the DataResult Controller stored the post data sent from the server in an array, and the view controller accessed these arrays to use the post data.
Now in the new structure, post data is passed through the factory to be made into view models, which are then accessed by the view controller.
The process of post data being made into view models through the factory all occur on the background thread.
This structure ensures that data processing tasks can be easily done on the background thread whenever view models need to process data from post data.
View model data structure
When processing post data into view models, you must manage the relationships between posts and view models whenever you perform insert, delete, or update tasks using post data.
The following two methods were available when implementing a way to manage the relationships between posts and view models.
- Real-time calculation and tracking of where each post’s view model is positioned on the view model array
- Management of each post’s view model through a relationship data structure between the posts and view models
In terms of search performance the method seen in 1.is more advantageous for real-time tracking of post and view model positions.
However, using this method means that if there’s a problem in the calculation or synchronization somewhere, it will cause problems with relationships between other post data as well. This means that we would have to refresh all relations each time there’s an update to the data.
On the Timeline, viewing posts and appending data arrays occur much more often than a certain post being inserted, deleted, or updated.
So we concluded that the structure would be more stable if the data structure handling relationships between posts and view models is refreshed whenever there’s a change made to a post, searching through the data structure to find the view model corresponding to the post when needed.
We included the process of making a view model from a post inside the DataResult Controller, so that view model arrays and view model relationships would be automatically refreshed each time a post is inserted or updated.
This removes the need to directly access a view model to delete or modify it. Now, whenever a post is deleted or edited, the view model array and relationship data structures automatically reflect the changes made.
Pros and cons of the new structure
Increased reusability of cells
With the smaller and more reusable cells, we lowered the cost needed to create new cells and were able to improve performance.
Better visibility when creating views and transferring data
As the view models now know which view each cell needs without having to make a pass through each view, data is only sent to views where they’re needed, simplifying the data transfer process, improving visibility and performance.
Background thread optimization
The creation of view models are structurally guaranteed to occur in the background thread. Developers only need to add a data processing logic to the part that creates view models to maximize background thread tasks, without having to think about when and where tasks will be required for a view.
As views are drawn using view
As views are drawn using view models using processed post data instead of raw post data, if you need to show the same data in a different way you only need to change the view model’s configuration or data, or change it into a different view model to draw it on the screen. This allows us to respond more flexibly to different requirements or specifications.
Costs of managing the relationships between posts and view models
The data structure is two-fold, creating view models by processing posts and then using the view models. Additional costs occur from creating additional view models and managing the relationships between post data and view models. Having to keep the two data structures in sync also adds additional pressure.
More lines of code and number of files
Now even adding a new simple view means that view models and cells need to be implemented to match the existing structure, which means there will be more lines of code and more files.
After the structure change, UI freezes have decreased and perceived user experience has improved. However, as the way views are drawn on the screen have changed, it was difficult to make an accurate performance comparison.
The QA team decided to measure the amount of time required to draw all the posts available to a user when scrolling down the Timeline. You can see the results below.
Comparing results from the previous version with the newer one, we can see that there was a 19-44% increase in performance when drawing posts on the screen.
|Previous version||New version||Improvement|
|Home screen (170 posts)||1’31.86″||1’13.95″||19%|
|Timeline screen (200 posts)||1’38.07″||1’13.15″||25%|
|Note screen (1012 posts)||8’40.50″||4’49.98″||44%|
More ideas for improvement
- In order to draw a cell on the screen, the height of the cell has to be calculated. This calculation is performed on the UI Thread. We expect that we can get better performance by performing this task on the background thread and have the UI Thread use the results.
- The currently defined view model protocol defines the process of creating cells and data configuration in the dequeueCell method, and allows for you to define additional methods that can be used in WillDisplayCell to the protocol. We expect that we can get better performance by only configuring the creation of cells on the dequeueCell method, and data on the WillDisplayCell method. This way data is only used when the views are visible on the screen.
In this post, I’ve gone over what the problems were with the previous structure, what we’ve done to improve it, and how it compares to the previous one. We’re always making updates so that our services can be further improved. I hope to make another blog post soon. Thank you for showing interest in Timeline!