(ASDK has been renamed Texture)
When it comes to view performance, it’s impossible not to mention UITableView, whose scrollability is constantly discussed and optimized. In the course of our exploration, we tried the following measures:
- Cell reuse, native Apple support
- Estimated Cell Height, iOS8 native support
- Manually cache the calculated height (or calculate it automatically using a framework such as FDTemplateLayoutCell)
- Prefetch API, iOS10 native support
- Asynchronously load cell contents, text and images
There are also some performance bottlenecks that normal UIViews, such as rounded corners and opaque, may encounter that were discussed in the first article and won’t be covered here.
We wondered, however, whether the cell’s asynchronous layout, image and text rendering could be optimized, and whether preloading could be more sophisticated and intelligent. There are still many problems waiting to be solved.
UITableView loading Cell process
Let’s take a look at the normal UITableView Cell loading process:
- CellForRowAtIndexPath, reads model
- Dequeue a cell from the reuse pool and call prepareForReuse to reset its state
- Assemble the model into the UITableViewCell
- Layout (time-consuming and uncacheable Autolayout), rendering (text and images are time-consuming), display
These operations occur just as the cell is about to enter the window. Understandably, these tasks are difficult to complete in 16ms (60fps), especially if the user is scrolling quickly and a large number of tasks pile up in the runloop, which makes things worse.
If you graphed the CPU usage during scrolling, it would look something like this (WWDC16 Session 219) :
Each time a cell is about to enter the screen, a spike is generated. During the rest of the idle time, the CPU load is quite low.
The natural question is, could this have been prevented if the tasks had been evenly distributed over a period of time, rather than concentrated in a single spot? If we can predict that a cell will be on screen soon and the CPU is idle, can we do some layout and rendering work ahead of time? That way, when the cell actually needs to be displayed, the backing store for the layout and rendering results is already in place and you can just give it to the view that is actually in charge of displaying it without causing a huge performance bump.
Create a new route ASTableNode/ASCollectionNode
First, the good news is that ASTableNode and cellNode, as part of ASDK, already have asynchronous layout and asynchronous rendering capabilities. Even without additional optimization, just use ASDK’s common asynchronous mechanism to delay time-consuming operations, which is a significant improvement over normal UITableView. Performance sawtooth is still there, but by moving it to background threads, the user experience of lag is less pronounced.
However, as if this were not enough, after entering the screen to render, there would be a brief white screen (waiting for the rendering to complete) before displaying the content. Since rendering can be done after the display, similarly, layout and rendering can be done some time before the display.
To do this, let’s start with some related classes:
- ASTableNode/ASCollectionNode, can be thought of as the UITableView/UICollectionView asynchronous version, inner packing the corresponding version of the original UIKit, and expand the function of a series of enabling them to realize asynchronous layout and rendering.
- ASInterfaceState: indicates the different display states of a node. Every ASDisplayNode has an interfaceState property, which is mainly used in tableNode/collectionNode. For a UITableViewCell, layout and rendering are usually done simultaneously in cellForRowAtIndexpath, but when it comes to fine-processing tasks, you need to separate out each of the different states to reduce the probability of a lag due to high CPU load at a given moment. ASInterfaceState is progressively divided into five states:
- None, the node will not enter the screen for a period of time
- MeasureLayout, which may come into the screen after some time, should be ready for layout and size calculations
- Preload, load the required data, such as image download, cache read, etc
- Display, which is about to come to the screen and start rendering, displays the contained text or image
- Visible, at least 1 pixel of the node (corresponding to the view) is already in the screen and is being displayed
For each cell, all the initialization/loading/layout/rendering work that would have been done at the same point in time is now evenly distributed to different states for preprocessing. As the user scrolls the list, the corresponding interfaceState is set according to the distance between the cell and the screen, and the work of different stages is triggered to achieve the purpose of even distribution. At the same time, because there is no need to run on the main thread, multiple cell work can be greatly improved by sharing background threads.
- ASDataController, which corresponds to ASTableNode, is responsible for managing delegate and dataSource methods in place of ASTableNode, such as initialization, insertion, deletion, and some proxy methods.
-
ASRangeController also corresponds to ASTableNode one by one, and can customize the layout, loading, and rendering work indexPath interval according to device performance, dynamically and efficiently adjust the interfaceState of each cell during scrolling to trigger different display stages of work. It plays a crucial role in smooth scrolling.
-
ASScrollDirection, which defines the scrolling direction of the list (up, down, left, and right). When ASRangeController adjusts the working interval of each stage, it generally needs to load more in the scrolling direction of the user, and the probability of the cell rolling out of the screen is low to return to the screen within a certain period of time, so it is allocated fewer resources.
In the era of asdk1.x, since there was no ASRangeController at that time, cell rendering would only be performed after entering the screen, that is, although the performance could reach 60fps, the rendering could not keep up when scrolling fast, and the “white screen” phenomenon would occur. After 2.x had ASRangeController, although there was still a white screen due to insufficient resources in the case of extremely fast scrolling, in general, this problem was significantly improved due to more reasonable resource allocation.
Some of the details
multithreading
How to evenly distribute the work to each thread without occupying too much CPU time?
Here’s how ASDK does it:
Obtain the number of cpus on the current device and multiply it by the workload of each CPU, for example, 4 x 5 = 20. That is, a maximum of 20 nodes are deployed in the same batch. (Although no strict documentation has been found to show that this calculation results in the highest efficiency, it should be better than batch processing to make the CPU time slice controllable.) Call Dispatch_apply and do a parallel layout of the same batch of 20 nodes and process 20 nodes per batch until all of them are processed
Listening for state changes
ASDisplayNode itself can not only depending on the state of the corresponding work, it also provides a series of methods for subclasses override, such as didEnterPreloadState/didExitVisibleState etc. In practice, since subclasses usually hold some resources (such as images) that they manage themselves, they need to control the allocation/reclamation of resources when they are displayed/off screen. Because each time point is relatively fine, as long as the work is evenly and reasonably allocated to the corresponding method, very precise and efficient resource scheduling can be achieved.
Memory management
ASRangeController often needs to manage off-screen nodes (which may have several screens of content for layout calculation and display at the same time). Preprocessing to reduce future workload is a typical “space for time” approach, which naturally increases the stress on memory.
To do this, ASRangeController provides parameters that allow developers to determine the scope of each stage to control memory:
ASLayoutRangeModeFull, use resources more, at the same time, the user experience is the best ASLayoutRangeMinimum, than the last one type ASLayoutRangeModeVisibleOnly save some resources, In the app to automatically set the background, is outside of the screen occupied by the node release resources, reduce the probability ASLayoutRangeModeLowMemory app were killed in the system, than on a more memory, in the province for the app back to the background, And the rangeController is not currently available on the screen (probably in the Navigation stack). To maximize the release of resources, we can call setTuningParameters, Make fine adjustments for each layoutRangeType in each mode. At the same time, ASDK will automatically switch back and forth in the above modes according to the node situation on the screen at this time, and load/release resources according to the specified parameter range, to achieve a balance between resources and performance.
The ASDK also includes a widget that shows how rangeController works:
There are four tabs in the popular page, and the corresponding Debug view in the lower right corner shows different working ranges of the four pages. The arrow represents the current scrolling direction. Since we configured the loading distance in the scrolling direction to be longer than in the opposite direction, you can see that the color block in the scrolling direction is longer.
Immediate practice
Instant iOS also struggled to optimize tabular performance in its early days. After reading an article by Ray Wenderlich, I was amazed by its excellent performance and started to get into the ASDK. At first, I tried to use it in message pages. Later, in long-term practice, I found that it can really solve the row height calculation and performance problems of tableView, and then gradually used it in other pages.
As mentioned in the first article of this series, we can’t rely heavily on advanced frameworks, but we are willing to embrace them. Looking at UITableView from ASDK’s point of view gives you more room to revisit the nature of list scrolling and take performance and resource allocation efficiency to a new level; Even in the future, there are still some ideas worth thinking about further away from ASDK.
Want to experience instant friends can download a look, recently added a lot of new ways 🙂
PS: we were hiring Android, crawler, the back end position, link: jike.ruguoapp.com/careers