實現異步下載網絡圖片


1數據準備, 創建一個appInfo模型 ,在視圖控制器中懶加載數組,實現字典轉模型,加載模型數組web


    NSMutableArray *data = [NSMutableArray array];
   
數組

    NSMutableArray *list = [NSMutableArray arrayWithCapacity:data.count];緩存


arrayWithCapacity: 容量,指定數組容量,在實例化數組的同時,準備好容量指定的空間,安全

假如是10,一次性在內存中準備好10個空間,再添加元素的時候就不會再次申請內存,若是增長第十一個元素,會再次開闢十個內存空間,網絡

[NSMutableArray array] 每次添加一個元素臨時申請內存空間app

顯然上面的性能比下面的要高不少異步


將可變數組變成不可變的—>有助於線程安全,外部不能修改佈局

使用代碼塊遍歷數組要比for快性能


2 同步加載圖片的效果 在主線程中直接加載測試

// 同步加載圖像
// 1. 模擬延時
NSLog(@"正在下載 %@", app.name);
[NSThread sleepForTimeInterval:0.5];
// 2. 同步加載網絡圖片
NSURL *url = [NSURL URLWithString:app.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];

cell.imageView.image = image;


存在問題:


  1. 若是網速慢,會卡爆了!影響用戶體驗

  2. 滾動表格,會重複下載圖像,形成用戶經濟上的損失,大量耗費流量

  3. 破法 —>異步加載圖片

3異步下載圖像 

全局操做隊列

//  全局隊列,統一管理全部下載操做@property (nonatomic, strong) NSOperationQueue *downloadQueue;
  • 懶加載

- (NSOperationQueue *)downloadQueue {    if (_downloadQueue == nil) {
        _downloadQueue = [[NSOperationQueue alloc] init];
    }    return _downloadQueue;
}

異步下載

// 異步加載圖像
// 1. 定義下載操做
// 異步加載圖像
NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{
    // 1. 模擬延時
    NSLog(@"正在下載 %@", app.name);
    [NSThread sleepForTimeInterval:0.5];
    // 2. 異步加載網絡圖片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 3. 主線程更新 UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        cell.imageView.image = image;
    }];
}];

// 2. 將下載操做添加到隊列

[self.downloadQueue addOperation:downloadOp];


存在的問題

下載完成後,不顯示圖片

緣由:使用的是系統提供的cell

    異步方法只是設置了圖像,可是沒有設置frame

    圖片加載後,一旦與cell交互,會調用layoutsubviews,從新調整佈局    




破法 : 0 使用佔位圖像

       1 使用自定義cell

      

cell.nameLabel.text = app.name;
cell.downloadLabel.text = app.download;

// 異步加載圖像
// 0. 佔位圖像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.iconView.image = placeholder;

// 1. 定義下載操做
NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{
    // 1. 模擬延時
    NSLog(@"正在下載 %@", app.name);
    [NSThread sleepForTimeInterval:0.5];
    // 2. 異步加載網絡圖片
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 3. 主線程更新 UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        cell.iconView.image = image;
    }];
}];

// 2. 將下載操做添加到隊列

[self.downloadQueue addOperation:downloadOp];

問題: 若是網絡圖片下載速度,不一致,同時用戶滾動圖片,可能顯示圖片錯行問題

         

  • 修改延時代碼,查看錯誤

// 1. 模擬延時
if (indexPath.row > 9) {
    [NSThread sleepForTimeInterval:3.0];
}

上下滾動一下表格便可看到 cell 複用的錯誤

破法 : MVC 

在模型中添加 image 屬性

#import <UIKit/UIKit.h>///  下載的圖像@property (nonatomic, strong) UIImage *image;

使用 MVC 更新表格圖像

  • 判斷模型中是否已經存在圖像

    if (app.image != nil) {  NSLog(@"加載模型圖像...");
      cell.iconView.image = app.image;  return cell;
    }
  • 下載完成後設置模型圖像

// 3. 主線程更新 UI[[NSOperationQueue mainQueue] addOperationWithBlock:^{    // 設置模型中的圖像
    app.image = image;    // 刷新表格
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];

問題

  • 若是圖像下載很慢,用戶滾動表格很快,會形成重複建立下載操做

  • 修改延時代碼

// 1. 模擬延時if (indexPath.row == 0) {
    [NSThread sleepForTimeInterval:10.0];
}

快速滾動表格,將第一行不斷「滾出/滾入」界面能夠查看操做被重複建立的問題

解決辦法

  • 操做緩衝池

緩衝池的選擇

所謂緩衝池,其實就是一個容器,可以存放多個對象

小結:選擇字典做爲操做緩衝池

緩衝池屬性

///  操做緩衝池

@property (nonatomicstrongNSMutableDictionary *operationCache;

- (NSMutableDictionary *)operationCache {    if (_operationCache == nil) {
       _operationCache = [NSMutableDictionary dictionary];
   }    return _operationCache;
}

修改代碼

// 異步加載圖像// 0. 佔位圖像UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.iconView.image = placeholder;// 判斷操做是否存在if (self.operationCache[app.icon] != nil) {    NSLog(@"正在玩命下載中...");    return cell;
}

// 2. 將操做添加到操做緩衝池[self.operationCache setObject:downloadOp forKey:app.icon];// 3. 將下載操做添加到隊列[self.downloadQueue addOperation:downloadOp];

修改佔位圖像的代碼位置,觀察會出現的問題

[self.operationCache removeObjectForKey:app.icon];

循環引用分析!

__weak typeof(self) weakSelf = self;
- (void)dealloc {    NSLog(@"我去了");
}

使用模型緩存圖像的問題

優勢

缺點

解決辦法

圖像緩存

///  圖像緩衝池@property (nonatomic, strong) NSMutableDictionary *imageCache;
- (NSMutableDictionary *)imageCache {    if (_imageCache == nil) {
       _imageCache = [[NSMutableDictionary alloc] init];
   }    return _imageCache;
}

斷網測試

問題

解決辦法

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    if (image != nil) {
        // 設置模型中的圖像
        [weakSelf.imageCache setObject:image forKey:app.icon];
        // 刷新表格
        [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }

}]



重構後的代碼

- (void)downloadImage:(NSIndexPath *)indexPath {    // 1. 根據 indexPath 獲取數據模型
   AppInfo *app = self.appList[indexPath.row];    // 2. 判斷操做是否存在
   if (self.operationCache[app.icon] != nil) {        NSLog(@"正在玩命下載中...");        return;
   }    // 3. 定義下載操做
   __weak typeof(self) weakSelf = self;
   NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{        // 1. 模擬延時
       NSLog(@"正在下載 %@", app.name);        if (indexPath.row == 0) {
           [NSThread sleepForTimeInterval:3.0];
       }        // 2. 異步加載網絡圖片
       NSURL *url = [NSURL URLWithString:app.icon];        NSData *data = [NSData dataWithContentsOfURL:url];        UIImage *image = [UIImage imageWithData:data];        // 3. 主線程更新 UI
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{            // 將下載操做從緩衝池中刪除
           [weakSelf.operationCache removeObjectForKey:app.icon];            if (image != nil) {                // 設置模型中的圖像
               [weakSelf.imageCache setObject:image forKey:app.icon];                // 刷新表格
               [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
           }
       }];
   }];    // 4. 將操做添加到操做緩衝池
   [self.operationCache setObject:downloadOp forKey:app.icon];    // 5. 將下載操做添加到隊列
   [self.downloadQueue addOperation:downloadOp];
}

內存警告

若是接收到內存警告,程序必定要作處理,工做中的程序必定要處理,不然後果很嚴重!!!

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

    // 1. 取消下載操做
    [self.downloadQueue cancelAllOperations];

    // 2. 清空緩衝池
    [self.operationCache removeAllObjects];
    [self.imageCache removeAllObjects];
}


黑名單

若是網絡正常,可是圖像下載失敗後,爲了不再次都從網絡上下載該圖像,可使用「黑名單」

@property (nonatomic, strong) NSMutableArray *blackList;
- (NSMutableArray *)blackList {    if (_blackList == nil) {
       _blackList = [NSMutableArray array];
   }    return _blackList;
}
if (image != nil) {
   // 設置模型中的圖像
   [weakSelf.imageCache setObject:image forKey:app.icon];
   // 刷新表格
   [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
} else {
   // 下載失敗記錄在黑名單中
   [weakSelf.blackList addObject:app.icon];
}

// 2.1 判斷黑名單
if ([self.blackList containsObject:app.icon]) {
    NSLog(@"已經將 %@ 加入黑名單...", app.icon);
    return;}

相關文章
相關標籤/搜索