Our iOS apps all contain a large number of images. Creating attractive views depends primarily on a large number of decorative images, all of which must first be retrieved from a remote server. If you have to obtain each image from the server again and again every time you open the application, then the user experience will definitely not achieve good results, so it is very necessary to locally cache remote images.
Load local pictures in two ways
1. Load the image through imageNamed: method
I have used this method to load the picture. Once the picture is loaded into memory, it will not be destroyed until the program exits. (In other words, imageNamed: There will be image cache function, which will be faster when accessing the image next time.)
When loading pictures in this way, the memory management of the pictures is not controlled by the programmer.
UIImage *image = [UIImage imageNamed: @“image”]
It means to create a UIImage object, not to say that image itself is an image, but image points to an image. When creating this object, the real image is not actually loaded into memory, but will not load it until the image is used.
As in the above example, if the image object is set to nil, if it is another object, then there is no strong pointer to point to an object, and the object will be destroyed; but even if image = nil, the image resource it points to will not be destroyed.
2. Load the image through imageWithContentsOfFile:
Use this method to load the picture. When the pointer to the image object is destroyed or points to other objects, the image object has no other strong pointer to it. The image object will be destroyed and will not stay in memory all the time.
Because there is no cache, if the same image is loaded multiple times, there will also be multiple image objects to occupy memory instead of using cached images.
Using this method, the file needs the full path (the same was true before loading files such as NSString and NSArray, such as stringWithContentsOfFile:, you will know that you need to pass the full path in when you see the file.)
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
Note that if the picture is in, this method cannot be used. So if you want to manage the memory of the image yourself (not wanting cached images), you should drag the image resources directly into the project instead of putting them in it.
Fast queues and slow queues
We set up two queues, one serial and one parallel. The images that are urgently requested on the screen enter the parallel queue (fastQueue), and the images that may be needed later enter the serial queue (slowQueue).
As far as the implementation of UITableView is concerned, this means that the table cells on the screen get the picture from the fastQueue, and the picture of each closed screen row is preloaded from the slowQueue.
No need to process images now
Suppose we want to request a page of information from the server containing 30 events, and once these content requests come back, we can queue up to prefetch each of them.
- (void)pageLoaded:(NSArray *)newEvents {
for (SGEvent *event in newEvents) {
[SGImageCache slowGetImageForURL: thenDo:nil];
}
}
slowGetImageForURL: This method adds images to the slowQueue queue, allowing them to be taken out one by one without blocking network communication.
thenDo: This code block is not implemented here because we don't need to do anything with the picture at the moment. All we need to do is make sure they are in the local disk cache and are ready to use when sliding the table on the screen.
Now we need to process the pictures
Tables displayed on the screen want their pictures to be displayed immediately, so implement it in the table cell subclass:
- (void)setEvent:(SGEvent *)event {
__weak SGEventCell *me = self;
[SGImageCache getImageForURL: thenDo:^(UIImage *image) {
= image; }
];
}
getImageForURL: This method adds the process of crawling images to the fastQueue queue, which means that they will be executed in parallel as long as the iOS system allows it. If the process of crawling an image already exists in the slowQueue queue, it will be moved to the fastQueue queue to avoid duplicate requests.
Always asynchronous
Wait, getImageForURL: Isn't it an asynchronous method? If you know that the image is already in cache, but don't want to use it immediately on the main thread? Intuition tells you that it is wrong.
Loading images from disk is too costly, and decompressing images will also cost a lot of resources. You can configure and add tables during the sliding process. This last thing you want to do when sliding the table is very dangerous because it will block the main thread and cause lag.
Use getImageForURL: to allow the disk loading action to be separated from the main thread, so when thenDo: the code block used for finishing work is executed, it already has a UIImage instance, so there is no risk of sliding and stuttering. If the image already exists in the local cache, the block of code used for finishing work will be executed in the next run cycle and the user will not notice the difference between the two. What they will notice is that the sliding will not stutter.
Now, you don't need to execute quickly
If the user quickly slides the table to the bottom, dozens or hundreds of table cells will appear on the screen and request image data from fastQueue, and then disappear from the screen very quickly. Suddenly this parallel queue will flood the network with a large number of image requests that are actually no longer needed. When the user finally stops sliding, the corresponding table cell views on the current screen will request their image to those that are not urgently needed, and therefore the network is blocked.
This is why wheremoveTaskToSlowQueueForURL: This method is produced.
// a table cell is going off screen-
(void)tableView:(UITableView *)table
didEndDisplayingCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath*)indexPath {
// we don't need it right now, so move it to the slow queue
[SGImageCache moveTaskToSlowQueueForURL:[[(id)cell event] imageURL]];
}
This ensures that there are only tasks that really need to be executed quickly in fastQueue. Any tasks that were previously thought to be executed quickly but are not needed now will be moved into slowQueue.
Highlights and choices
There are already quite a lot of iOS image cache libraries. Some of them are only for certain application scenarios, and some libraries provide certain scalability for different scenarios. Our library is not specifically designed for certain application scenarios, nor does it have many large and comprehensive features. We have three basic key points for our users:
Key point 1: The best frame rate
Many libraries are very focused on this, using some highly customized and complex approaches, although the benchmarks do not show decisively so effective. We found that the best frame rate is determined by these:
Disconnects access to disk (and almost everything else) from the main thread.
Use UIImage's memory cache to avoid unnecessary disk access and image decompression.
Key 2: Make the most important pictures show first
Most libraries consider making queue management a concern. This is almost the most important point for our application.
Having the right image displayed on the screen at the right time boils down to a simple question: "Do we need it to display now or after a while?". Those images that need to be displayed immediately are loaded in parallel, while everything else is added to the serial queue. All the things that were urgent before but not urgent now will be distributed from fastQueue to slowQueue. And when fastQueue is working, slowQueue is in a suspended state.
This allows those pictures that need to be displayed to be accessed separately from the network, and also ensures that a picture that is not needed to be displayed can become a picture that needs to be displayed later, because it has been stored in the cache and ready to be used for display at any time.
Key point 3: As simple as possible API
Most libraries do this. Many libraries provide classification of UIImageView to hide details, and many libraries make the process of crawling an image as convenient as possible. For three things we often do, our library has selected three main methods:
Get a picture quickly
__weak SGEventCell *me = self;[SGImageCache getImageForURL: thenDo:^(UIImage *image) { = image;}];
Waiting for a picture we need in a while
[SGImageCache slowGetImageForURL: thenDo:nil];
Notify cache of an urgently needed image no longer needs to be displayed immediately
[SGImageCache moveTaskToSlowQueueForURL:];
in conclusion
By focusing on prefetching, queue management, removing time-consuming tasks from the main thread, and relying on UIImage's built-in memory cache, we strive to get good results from a simple package.