For example, when we use Netease News App, not all of the news we watch is interesting. Sometimes we just skip quickly and want to skip the content we don't like, but as long as we skip through, the picture starts loading, so the user experience is not good and memory is wasted.
At this time, we can use lazy loading technology, when the interface slides or slides to decelerate, do not load pictures, only when the user no longer slides and the deceleration effect stops.
At the beginning, I used SDWebImage to load images asynchronously, but at the end of the experiment, there was a reuse bug, because although SDWebImage implemented the asynchronous loading cache, when the images were loaded, the requests would load the cached images directly. Note that the key point is that if lazy was loaded, the images on the cell would be loaded without network requests during the sliding process. When reuse occurs, when you stop to make network requests, it will change back to the current Cell image, about 1-2 seconds delay (not to mention delay, no request, nor no caching). How to solve it? At this time, we need to define a UI Image property in the Model object, download the picture asynchronously, and use the image path that has been cached in the sandbox. In the cellForRowAtIndexPath method, we can judge whether the UIImage object is empty or not. If it is empty, we can make a network request. If it is not empty, we can directly assign it to the cell's imageView object, which can solve the problem of short-term image reuse.
@ My code uses the asynchronous loading caching class written by myself, and the lazy loading of SDWebImage images will be given in later chapters. (Why not? Because SDWebImage I used to use it again and didn't care about the names and paths of the images stored in sandboxes, but to achieve lazy loading, we must get the image paths, so we are looking for SDWebImage such as How did it take some time to store the image path?
- @modelclass
- #import <Foundation/Foundation.h>
- @interface NewsItem : NSObject
- @property (nonatomic,copy) NSString * newsTitle;
- @property (nonatomic,copy) NSString * newsPicUrl;
- @property (nonatomic,retain) UIImage * newsPic; //Store each news's own image object
- - (id)initWithDictionary:(NSDictionary *)dic;
- //Processing parsing
- + (NSMutableArray *)handleData:(NSData *)data;
- @end
- #import "NewsItem.h"
- #import "ImageDownloader.h"
- @implementation NewsItem
- - (void)dealloc
- {
- self.newsTitle = nil;
- self.newsPicUrl = nil;
- self.newsPic = nil;
- [super dealloc];
- }
- - (id)initWithDictionary:(NSDictionary *)dic
- {
- self = [super init];
- if (self) {
- self.newsTitle = [dic objectForKey:@"title"];
- self.newsPicUrl = [dic objectForKey:@"picUrl"];
- //Loading images from local sandboxes
- ImageDownloader * downloader = [[[ImageDownloader alloc] init] autorelease];
- self.newsPic = [downloader loadLocalImage:_newsPicUrl];
- }
- return self;
- }
- + (NSMutableArray *)handleData:(NSData *)data;
- {
- //Parsing data
- NSError * error = nil;
- NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
- NSMutableArray * originalArray = [dic objectForKey:@"news"];
- //Encapsulating data objects
- NSMutableArray * resultArray = [NSMutableArray array];
- for (int i=0 ;i<[originalArray count]; i++) {
- NSDictionary * newsDic = [originalArray objectAtIndex:i];
- NewsItem * item = [[NewsItem alloc] initWithDictionary:newsDic];
- [resultArray addObject:item];
- [item release];
- }
- return resultArray;
- }
- @end
@ model class#import <Foundation/Foundation.h> @interface NewsItem : NSObject @property (nonatomic,copy) NSString * newsTitle; @property (nonatomic,copy) NSString * newsPicUrl; @ property (nonatomic,retain) UIImage * newsPic; // Store each news's own image object - (id)initWithDictionary:(NSDictionary *)dic; // Processing Analysis + (NSMutableArray *)handleData:(NSData *)data; @end #import "NewsItem.h" #import "ImageDownloader.h" @implementation NewsItem - (void)dealloc { self.newsTitle = nil; self.newsPicUrl = nil; self.newsPic = nil; [super dealloc]; } - (id)initWithDictionary:(NSDictionary *)dic { self = [super init]; if (self) { self.newsTitle = [dic objectForKey:@"title"]; self.newsPicUrl = [dic objectForKey:@"picUrl"]; // Loading images from local sandboxes ImageDownloader * downloader = [[[ImageDownloader alloc] init] autorelease]; self.newsPic = [downloader loadLocalImage:_newsPicUrl]; } return self; } + (NSMutableArray *)handleData:(NSData *)data; { // parsing data NSError * error = nil; NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; NSMutableArray * originalArray = [dic objectForKey:@"news"]; // Encapsulating data objects NSMutableArray * resultArray = [NSMutableArray array]; for (int i=0 ;i<[originalArray count]; i++) { NSDictionary * newsDic = [originalArray objectAtIndex:i]; NewsItem * item = [[NewsItem alloc] initWithDictionary:newsDic]; [resultArray addObject:item]; [item release]; } return resultArray; } @end
- @Picture download class
- #import <Foundation/Foundation.h>
- @class NewsItem;
- @interface ImageDownloader : NSObject
- @property (nonatomic,copy) NSString * imageUrl;
- @property (nonatomic,retain) NewsItem * newsItem; //Download the news that the image belongs to
- //After the image download is completed, the callback is realized by block
- @property (nonatomic,copy) void (^completionHandler)(void);
- //Start downloading images
- - (void)startDownloadImage:(NSString *)imageUrl;
- //Loading images locally
- - (UIImage *)loadLocalImage:(NSString *)imageUrl;
- @end
- #import "ImageDownloader.h"
- #import "NewsItem.h"
- @implementation ImageDownloader
- - (void)dealloc
- {
- self.imageUrl = nil;
- Block_release(_completionHandler);
- [super dealloc];
- }
- #pragma mark - asynchronous loading
- - (void)startDownloadImage:(NSString *)imageUrl
- {
- self.imageUrl = imageUrl;
- //First, determine whether the local sandbox has an image, there is direct access, there is no download, download and save
- //In Download Images, a subfolder of Caches with sandboxes
- UIImage * image = [self loadLocalImage:imageUrl];
- if (image == nil) {
- //No sandbox, Download
- //Asynchronous download, allocated to concurrent queues generated by default in program processes
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- //Download images in multithreading
- NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
- //Cache pictures
- [imageData writeToFile:[self imageFilePath:imageUrl] atomically:YES];
- //Go back to the main thread to complete UI settings
- dispatch_async(dispatch_get_main_queue(), ^{
- //Save the downloaded image into the newsItem object
- UIImage * image = [UIImage imageWithData:imageData];
- self.newsItem.newsPic = image;
- //Callback using block to notify the completion of image download
- if (_completionHandler) {
- _completionHandler();
- }
- });
- });
- }
- }
- #pragma mark - Load local images
- - (UIImage *)loadLocalImage:(NSString *)imageUrl
- {
- self.imageUrl = imageUrl;
- //Getting image paths
- NSString * filePath = [self imageFilePath:self.imageUrl];
- UIImage * image = [UIImage imageWithContentsOfFile:filePath];
- if (image != nil) {
- return image;
- }
- return nil;
- }
- #pragma mark - Get image path
- - (NSString *)imageFilePath:(NSString *)imageUrl
- {
- //Get the caches folder path
- NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
- //Create the Download Images folder
- NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"];
- NSFileManager * fileManager = [NSFileManager defaultManager];
- if (![fileManager fileExistsAtPath:downloadImagesPath]) {
- [fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];
- }
- #Pagma mark mosaic image file path in sandbox, because the image URL has "/", to be replaced before storage, optional "" instead.
- NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
- NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];
- return imageFilePath;
- }
- @end
@ Picture download class#import <Foundation/Foundation.h> @class NewsItem; @interface ImageDownloader : NSObject @property (nonatomic,copy) NSString * imageUrl; @ property (nonatomic,retain) NewsItem * newsItem; // Download the news to which the image belongs // After the image download is completed, the callback is realized by block @property (nonatomic,copy) void (^completionHandler)(void); // Start downloading images - (void)startDownloadImage:(NSString *)imageUrl; // Loading images locally - (UIImage *)loadLocalImage:(NSString *)imageUrl; @end #import "ImageDownloader.h" #import "NewsItem.h" @implementation ImageDownloader - (void)dealloc { self.imageUrl = nil; Block_release(_completionHandler); [super dealloc]; } # pragma mark - asynchronous loading - (void)startDownloadImage:(NSString *)imageUrl { self.imageUrl = imageUrl; // First determine whether the local sandbox has an image, there is direct access, there is no download, download and save. // In Download Images, a subfolder of Caches with sandboxes UIImage * image = [self loadLocalImage:imageUrl]; if (image == nil) { // No sandbox, Download // Asynchronous download, allocating concurrent queues generated by default in program processes dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Download images in multithreading NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; // Cache Pictures [imageData writeToFile:[self imageFilePath:imageUrl] atomically:YES]; // Go back to the main thread to complete UI settings dispatch_async(dispatch_get_main_queue(), ^{ // Save the downloaded image into the newsItem object UIImage * image = [UIImage imageWithData:imageData]; self.newsItem.newsPic = image; // Callback using block to notify the completion of image download if (_completionHandler) { _completionHandler(); } }); }); } } # pragma mark - Load local image - (UIImage *)loadLocalImage:(NSString *)imageUrl { self.imageUrl = imageUrl; // Getting Image Path NSString * filePath = [self imageFilePath:self.imageUrl]; UIImage * image = [UIImage imageWithContentsOfFile:filePath]; if (image != nil) { return image; } return nil; } # pragma mark - Get image path - (NSString *)imageFilePath:(NSString *)imageUrl { // Get the caches folder path NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // Create the Download Images folder NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"]; NSFileManager * fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:downloadImagesPath]) { [fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil]; } # Pagma mark mosaic image file path in sandbox, because the image URL has "/", to be replaced before storage, optional "" instead. NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName]; return imageFilePath; } @end
- @Only the key code is given here.,Network Request,data processing,custom cell Self-resolving
- #pragma mark - Table view data source
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- // Return the number of sections.
- return 1;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- // Return the number of rows in the section.
- if (_dataArray.count == 0) {
- return 10;
- }
- return [_dataArray count];
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *cellIdentifier = @"Cell";
- NewsListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier ];
- if (!cell) {
- cell = [[[NewsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
- }
- NewsItem * item = [_dataArray objectAtIndex:indexPath.row];
- cell.titleLabel.text = item.newsTitle;
- //Judging whether the news to be displayed has an image or not
- if (item.newsPic == nil) {
- //No image downloads
- cell.picImageView.image = nil;
- NSLog(@"dragging = %d,decelerating = %d",self.tableView.dragging,self.tableView.decelerating);
- //The timing and number of executions
- if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {
- [self startPicDownload:item forIndexPath:indexPath];
- }
- }else{
- //Direct display with images
- NSLog(@"1111");
- cell.picImageView.image = item.newsPic;
- }
- cell.titleLabel.text = [NSString stringWithFormat:@"indexPath.row = %ld",indexPath.row];
- return cell;
- }
- - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- return [NewsListCell cellHeight];
- }
- //Start downloading images
- - (void)startPicDownload:(NewsItem *)item forIndexPath:(NSIndexPath *)indexPath
- {
- //Create Image Downloader
- ImageDownloader * downloader = [[ImageDownloader alloc] init];
- //The Downloader Downloads the image of which news. After downloading, the news saves the image.
- downloader.newsItem = item;
- //Input callback function after completion of download
- [downloader setCompletionHandler:^{
- //The callback part to be executed after downloading, the implementation of block
- //Get the cell object according to indexPath and load the image
- #pragma mark cell ForRow AtIndex Path -> Not seen
- NewsListCell * cell = (NewsListCell *)[self.tableView cellForRowAtIndexPath:indexPath];
- cell.picImageView.image = downloader.newsItem.newsPic;
- }];
- //Start downloading
- [downloader startDownloadImage:item.newsPicUrl];
- [downloader release];
- }
- - (void)loadImagesForOnscreenRows
- {
- #pragma mark indexPaths for Visible Rows -> Not seen
- //Get the cell that the tableview is displaying on the window and load the image on the cell. Cell objects that need to be displayed on the line can be retrieved through indexPath
- NSArray * visibleCells = [self.tableView indexPathsForVisibleRows];
- for (NSIndexPath * indexPath in visibleCells) {
- NewsItem * item = [_dataArray objectAtIndex:indexPath.row];
- if (item.newsPic == nil) {
- //If the news hasn't downloaded the image, start downloading.
- [self startPicDownload:item forIndexPath:indexPath];
- }
- }
- }
- #pragma mark - key to delayed loading
- //tableView stops dragging and scrolling
- - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- {
- //If the tableview stops scrolling, start loading the image
- if (!decelerate) {
- [self loadImagesForOnscreenRows];
- }
- NSLog(@"%s__%d__|%d",__FUNCTION__,__LINE__,decelerate);
- }
- - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
- {
- //If the tableview stops scrolling, start loading the image
- [self loadImagesForOnscreenRows];
- }
@ Only key code, network request, data processing and custom cell are given here.
pragma mark - Table view data source
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (_dataArray.count == 0) {
return 10;
}
return [_dataArray count];
}-
(UITableViewCell )tableView:(UITableView )tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"Cell";
NewsListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier ];
if (!cell) {
cell = [[[NewsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}NewsItem * item = [_dataArray objectAtIndex:indexPath.row];
cell.titleLabel.text = item.newsTitle;
// Judging whether the news to be displayed has an image or not
if (item.newsPic == nil) {
// No image downloads
cell.picImageView.image = nil;NSLog(@"dragging = %d,decelerating = %d",self.tableView.dragging,self.tableView.decelerating); // The timing and number of executions if (self.tableView.dragging == NO && self.tableView.decelerating == NO) { [self startPicDownload:item forIndexPath:indexPath]; }
}else{
// Direct display with images
NSLog(@"1111");
cell.picImageView.image = item.newsPic;}
cell.titleLabel.text = [NSString stringWithFormat:@"indexPath.row = %ld",indexPath.row];
return cell;
} (CGFloat)tableView:(UITableView )tableView heightForRowAtIndexPath:(NSIndexPath )indexPath
{
return [NewsListCell cellHeight];
}
// Start downloading images
- (void)startPicDownload:(NewsItem )item forIndexPath:(NSIndexPath )indexPath
{
// Create Image Downloader
ImageDownloader * downloader = [[ImageDownloader alloc] init];
//The Downloader Downloads the image of which news. After downloading, the news saves the image. downloader.newsItem = item; //Input callback function after completion of download [downloader setCompletionHandler:^{ //The callback part to be executed after downloading, the implementation of block //Get the cell object according to indexPath and load the image
Pragma mark cell ForRow AtIndex Path -> Not seen
NewsListCell * cell = (NewsListCell *)[self.tableView cellForRowAtIndexPath:indexPath]; cell.picImageView.image = downloader.newsItem.newsPic; }]; //Start downloading [downloader startDownloadImage:item.newsPicUrl]; [downloader release];
}
- (void)loadImagesForOnscreenRows
{
Pragma mark index Paths for Visible Rows -> Not seen
//Get the cell that the tableview is displaying on the window and load the image on the cell. Cell objects that need to be displayed on the line can be retrieved through indexPath NSArray * visibleCells = [self.tableView indexPathsForVisibleRows]; for (NSIndexPath * indexPath in visibleCells) { NewsItem * item = [_dataArray objectAtIndex:indexPath.row]; if (item.newsPic == nil) { //If the news hasn't downloaded the image, start downloading. [self startPicDownload:item forIndexPath:indexPath]; } }
}
pragma mark - the key to delayed loading
// tableView stops dragging and scrolling
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// If the tableview stops scrolling, start loading the image
if (!decelerate) {
[self loadImagesForOnscreenRows]; } NSLog(@"%s__%d__|%d",__FUNCTION__,__LINE__,decelerate);
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// If the tableview stops scrolling, start loading the image
[self loadImagesForOnscreenRows];
}