Lazy lazy loading (lazy loading) UITableView

Posted by juddster on Sun, 09 Jun 2019 01:04:44 +0200

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?

  1. @modelclass  
  2. #import <Foundation/Foundation.h>  
  3.   
  4. @interface NewsItem : NSObject  
  5.   
  6. @property (nonatomic,copyNSString * newsTitle;  
  7. @property (nonatomic,copyNSString * newsPicUrl;  
  8. @property (nonatomic,retainUIImage * newsPic; //Store each news's own image object  
  9.   
  10. - (id)initWithDictionary:(NSDictionary *)dic;  
  11.   
  12. //Processing parsing  
  13. + (NSMutableArray *)handleData:(NSData *)data;  
  14. @end  
  15.   
  16.   
  17. #import "NewsItem.h"  
  18. #import "ImageDownloader.h"  
  19.   
  20. @implementation NewsItem  
  21.   
  22. - (void)dealloc  
  23. {  
  24.     self.newsTitle = nil;  
  25.     self.newsPicUrl = nil;  
  26.     self.newsPic = nil;  
  27.     [super dealloc];  
  28. }  
  29.   
  30. - (id)initWithDictionary:(NSDictionary *)dic  
  31. {  
  32.     self = [super init];  
  33.     if (self) {  
  34.   
  35.   
  36.         self.newsTitle = [dic objectForKey:@"title"];  
  37.         self.newsPicUrl = [dic objectForKey:@"picUrl"];  
  38.           
  39.         //Loading images from local sandboxes  
  40.         ImageDownloader * downloader = [[[ImageDownloader alloc] init] autorelease];  
  41.         self.newsPic = [downloader loadLocalImage:_newsPicUrl];  
  42.   
  43.     }  
  44.   
  45.     return self;  
  46. }  
  47.   
  48. + (NSMutableArray *)handleData:(NSData *)data;  
  49. {  
  50.   
  51.         //Parsing data  
  52.         NSError * error = nil;  
  53.         NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];  
  54.         NSMutableArray * originalArray = [dic objectForKey:@"news"];  
  55.   
  56.         //Encapsulating data objects  
  57.         NSMutableArray * resultArray = [NSMutableArray array];  
  58.       
  59.         for (int i=0 ;i<[originalArray count]; i++) {  
  60.             NSDictionary * newsDic = [originalArray objectAtIndex:i];  
  61.             NewsItem * item = [[NewsItem alloc] initWithDictionary:newsDic];  
  62.             [resultArray addObject:item];  
  63.             [item release];  
  64.         }  
  65.   
  66.         return resultArray;  
  67.   
  68. }  
  69.   
  70. @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
  1. @Picture download class  
  2. #import <Foundation/Foundation.h>  
  3.   
  4.   
  5. @class NewsItem;  
  6.   
  7.   
  8. @interface ImageDownloader : NSObject  
  9.   
  10.   
  11. @property (nonatomic,copyNSString * imageUrl;  
  12. @property (nonatomic,retainNewsItem * newsItem; //Download the news that the image belongs to  
  13.   
  14.   
  15. //After the image download is completed, the callback is realized by block  
  16. @property (nonatomic,copyvoid (^completionHandler)(void);  
  17.   
  18.   
  19. //Start downloading images  
  20. - (void)startDownloadImage:(NSString *)imageUrl;  
  21.   
  22.   
  23. //Loading images locally  
  24. - (UIImage *)loadLocalImage:(NSString *)imageUrl;  
  25.   
  26.   
  27. @end  
  28.   
  29.   
  30.   
  31.   
  32. #import "ImageDownloader.h"  
  33. #import "NewsItem.h"  
  34.   
  35.   
  36. @implementation ImageDownloader  
  37.   
  38.   
  39. - (void)dealloc  
  40. {  
  41.     self.imageUrl = nil;  
  42.     Block_release(_completionHandler);  
  43.     [super dealloc];  
  44. }  
  45.   
  46.   
  47.   
  48.   
  49. #pragma mark - asynchronous loading  
  50. - (void)startDownloadImage:(NSString *)imageUrl  
  51. {  
  52.   
  53.   
  54.     self.imageUrl = imageUrl;  
  55.   
  56.   
  57.     //First, determine whether the local sandbox has an image, there is direct access, there is no download, download and save  
  58.     //In Download Images, a subfolder of Caches with sandboxes  
  59.     UIImage * image = [self loadLocalImage:imageUrl];  
  60.   
  61.   
  62.     if (image == nil) {  
  63.   
  64.   
  65.         //No sandbox, Download  
  66.         //Asynchronous download, allocated to concurrent queues generated by default in program processes  
  67.         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  68.   
  69.   
  70.             //Download images in multithreading  
  71.             NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];  
  72.   
  73.   
  74.             //Cache pictures  
  75.             [imageData writeToFile:[self imageFilePath:imageUrl] atomically:YES];  
  76.   
  77.   
  78.             //Go back to the main thread to complete UI settings  
  79.             dispatch_async(dispatch_get_main_queue(), ^{  
  80.   
  81.   
  82.                 //Save the downloaded image into the newsItem object  
  83.                 UIImage * image = [UIImage imageWithData:imageData];  
  84.                 self.newsItem.newsPic = image;  
  85.   
  86.   
  87.                 //Callback using block to notify the completion of image download  
  88.                 if (_completionHandler) {  
  89.                     _completionHandler();  
  90.                 }  
  91.                   
  92.             });  
  93.               
  94.         });  
  95.     }  
  96.       
  97. }  
  98.   
  99. #pragma mark - Load local images  
  100. - (UIImage *)loadLocalImage:(NSString *)imageUrl  
  101. {  
  102.   
  103.     self.imageUrl = imageUrl;  
  104.   
  105.   
  106.     //Getting image paths  
  107.     NSString * filePath = [self imageFilePath:self.imageUrl];  
  108.   
  109.   
  110.     UIImage * image = [UIImage imageWithContentsOfFile:filePath];  
  111.   
  112.   
  113.     if (image != nil) {  
  114.         return image;  
  115.     }  
  116.   
  117.     return nil;  
  118. }  
  119.   
  120. #pragma mark - Get image path  
  121. - (NSString *)imageFilePath:(NSString *)imageUrl  
  122. {  
  123.     //Get the caches folder path  
  124.     NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];  
  125.   
  126.   
  127.     //Create the Download Images folder  
  128.     NSString * downloadImagesPath = [cachesPath stringByAppendingPathComponent:@"DownloadImages"];  
  129.     NSFileManager * fileManager = [NSFileManager defaultManager];  
  130.     if (![fileManager fileExistsAtPath:downloadImagesPath]) {  
  131.   
  132.   
  133.         [fileManager createDirectoryAtPath:downloadImagesPath withIntermediateDirectories:YES attributes:nil error:nil];  
  134.     }  
  135.   
  136.   
  137. #Pagma mark mosaic image file path in sandbox, because the image URL has "/", to be replaced before storage, optional "" instead.  
  138.     NSString * imageName = [imageUrl stringByReplacingOccurrencesOfString:@"/" withString:@"_"];  
  139.     NSString * imageFilePath = [downloadImagesPath stringByAppendingPathComponent:imageName];  
  140.   
  141.   
  142.     return imageFilePath;  
  143. }  
  144.   
  145. @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


  1. @Only the key code is given here.,Network Request,data processing,custom cell Self-resolving  
  2.   
  3. #pragma mark - Table view data source  
  4.   
  5. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView  
  6. {  
  7.     // Return the number of sections.  
  8.     return 1;  
  9. }  
  10.   
  11. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
  12. {  
  13.     // Return the number of rows in the section.  
  14.     if (_dataArray.count == 0) {  
  15.         return 10;  
  16.     }  
  17.     return [_dataArray count];  
  18. }  
  19.   
  20. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  21. {  
  22.     static NSString *cellIdentifier = @"Cell";  
  23.     NewsListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier ];  
  24.     if (!cell) {  
  25.         cell = [[[NewsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];  
  26.     }  
  27.   
  28.     NewsItem * item = [_dataArray objectAtIndex:indexPath.row];  
  29.   
  30.     cell.titleLabel.text = item.newsTitle;  
  31.   
  32.     //Judging whether the news to be displayed has an image or not  
  33.   
  34.     if (item.newsPic == nil) {  
  35.         //No image downloads  
  36.         cell.picImageView.image = nil;  
  37.           
  38.         NSLog(@"dragging = %d,decelerating = %d",self.tableView.dragging,self.tableView.decelerating);  
  39.         //The timing and number of executions  
  40.         if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {  
  41.             [self startPicDownload:item forIndexPath:indexPath];  
  42.         }  
  43.   
  44.     }else{  
  45.         //Direct display with images  
  46.         NSLog(@"1111");  
  47.         cell.picImageView.image = item.newsPic;  
  48.   
  49.     }  
  50.       
  51.     cell.titleLabel.text = [NSString stringWithFormat:@"indexPath.row = %ld",indexPath.row];  
  52.   
  53.     return cell;  
  54. }  
  55.   
  56. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  
  57. {  
  58.     return [NewsListCell cellHeight];  
  59. }  
  60.   
  61. //Start downloading images  
  62. - (void)startPicDownload:(NewsItem *)item forIndexPath:(NSIndexPath *)indexPath  
  63. {  
  64.     //Create Image Downloader  
  65.     ImageDownloader * downloader = [[ImageDownloader alloc] init];  
  66.   
  67.     //The Downloader Downloads the image of which news. After downloading, the news saves the image.  
  68.     downloader.newsItem = item;  
  69.   
  70.     //Input callback function after completion of download  
  71.     [downloader setCompletionHandler:^{  
  72.   
  73.         //The callback part to be executed after downloading, the implementation of block  
  74.         //Get the cell object according to indexPath and load the image  
  75. #pragma mark cell ForRow AtIndex Path -> Not seen  
  76.         NewsListCell * cell = (NewsListCell *)[self.tableView cellForRowAtIndexPath:indexPath];  
  77.         cell.picImageView.image = downloader.newsItem.newsPic;  
  78.   
  79.     }];  
  80.   
  81.     //Start downloading  
  82.     [downloader startDownloadImage:item.newsPicUrl];  
  83.   
  84.     [downloader release];  
  85. }  
  86.   
  87.   
  88. - (void)loadImagesForOnscreenRows  
  89. {  
  90. #pragma mark indexPaths for Visible Rows -> Not seen  
  91.     //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  
  92.     NSArray * visibleCells = [self.tableView indexPathsForVisibleRows];  
  93.     for (NSIndexPath * indexPath in visibleCells) {  
  94.         NewsItem * item = [_dataArray objectAtIndex:indexPath.row];  
  95.         if (item.newsPic == nil) {  
  96.             //If the news hasn't downloaded the image, start downloading.  
  97.             [self startPicDownload:item forIndexPath:indexPath];  
  98.         }  
  99.     }  
  100. }  
  101.   
  102. #pragma mark - key to delayed loading  
  103. //tableView stops dragging and scrolling  
  104. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate  
  105. {  
  106.     //If the tableview stops scrolling, start loading the image  
  107.     if (!decelerate) {  
  108.   
  109.         [self loadImagesForOnscreenRows];  
  110.     }  
  111.      NSLog(@"%s__%d__|%d",__FUNCTION__,__LINE__,decelerate);  
  112. }  
  113.   
  114. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView  
  115. {  
  116.     //If the tableview stops scrolling, start loading the image  
  117.     [self loadImagesForOnscreenRows];  
  118.   
  119. }  
@ 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 &amp;&amp; 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];

}

Topics: network