Download Images from a server to display on the Co

2019-06-23 16:41发布

问题:

I am working on a product application where user could sell/buy. This application is based on collection view. Collection view has collection cell where it displays product image thumbnail.

The following code gets products images from the server and it waits to download all images and then display them in the cells. The following code works but user is waiting 10-20 seconds to see all products. Is there a better way to handle ?

- (void)viewDidLoad {
     [super viewDidLoad];
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
     dispatch_async(queue, ^(void) {
        [self loadFromURL];
     dispatch_async(dispatch_get_main_queue(), ^{
     });
  });
}

-(void)loadFromURL {
    NSURL *url = [NSURL URLWithString:@"http://myURL/productAll.php"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [AFJSONResponseSerializer serializer];

   [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
     {        
      pElements= (NSMutableArray *)responseObject;
      [collectionView reloadData];

     } 
     failure:^(AFHTTPRequestOperation *operation, NSError *error) 
     {        
       UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
     [alertView show];
   }];    
  [operation start];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return pElements.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    ProductCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    cell.backgroundColor=[UIColor whiteColor];
    NSString *arrayResult = [[pElements objectAtIndex:indexPath.row] objectForKey:@"image"];
    NSData *data = [[NSData alloc] initWithBase64EncodedString:arrayResult options:NSDataBase64DecodingIgnoreUnknownCharacters];
    cell.productImage.image = [[UIImage alloc] initWithData:data];
    cell.productImage.layer.cornerRadius = 10;
    cell.productImage.clipsToBounds = YES;
    return cell;
}

回答1:

You have a response from the server in which the image data for all of the images is base-64 encoded in the response. This means that the response is likely very large and won't be shown to the user until everything is downloaded.

Instead, you might consider refactoring your server code to not include the image data in base-64 format, but rather to just include a URL (or some identifier) that can be used to retrieve the image later. Your response should be much smaller and should be able to be processed much more quickly.

Then, when cellForItemAtIndexPath is called, rather than extracting the image data out of the original response, you lazily (and asynchronously) request the image for the cell. AFNetworking provides a nice UIImageView category in UIImageView+AFNetworking that asynchronously retrieves images from network source. (And using this category gets you out of the weeds of lots of subtle issues when doing asynchronous image retrieval.)

By the way, if your images are of varying sizes, you might want to include the dimensions of the image in the original request so that the cells and their image views can be appropriately sized up front, rather than resizing them as the images are retrieved.

--

A couple of observations:

  1. You don't need to dispatch [self loadFromURL] to a background queue, as that's already asynchronous. And I'd probably use GET of the request

  2. You can't just cast responseObject to NSMutableArray, because it probably is not mutable. You really should use NSArray or use mutableCopy if you really need it to be mutable.

  3. You're doing some cell configuration in cellForItemAtIndexPath. Most of that (clipping, background color, etc.) can be done right in IB, so I would do it there rather than doing it programmatically. The only thing you might need to do programmatically is the rounding of the corners (and, even that, I'd probably do with a IBDesignable subclass, though that's beyond the scope of this question).

Thus, assuming (a) your array has a new property called imageURL which is the URL of the image; and (b) the cell has a fixed sized image view, you could do something like:

@interface ViewController ()

@property (nonatomic, strong) AFHTTPRequestOperationManager *manager;
@property (nonatomic, strong) NSArray *pElements;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.manager = [AFHTTPRequestOperationManager manager];

    [self loadFromURL];
}

-(void)loadFromURL {
    NSString *urlString = @"http://myURL/productAll.php";

    [self.manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
        self.pElements = responseObject;
        [self.collectionView reloadData];
    } failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Product" message:[error localizedDescription]delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alertView show];
    }];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.pElements.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    NSString *imageURL = self.pElements[indexPath.row][@"imageURL"];

    [cell.productImage setImageWithURL:[NSURL URLWithString:imageURL]];
    cell.productImage.layer.cornerRadius = 10;

    return cell;
}

@end