图片加载

  1. 图片在主线程下载–>网速慢, 会出现界面卡顿

  2. 将下载放在子线程:

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 交互,会调用 cell 的 layoutSubviews
    方法,重新调整 cell 的布局

解决办法:

  • 使用占位图片

// 0. 占位图像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.imageView.image = placeholder;

问题: 每次和 cell 交互,layoutSubviews 方法会根据图像的大小自动调整 imageView 的尺寸
解决办法: 自定义cell


问题: 这样图片大小变化问题解决了, 但是由于网络图片加载速度不一致, 用户滚动列表, 可能会出现图片错行
解决办法: MVC–将网络下载的图片保存在模型中, 这样设置图片的时候从cell对应的模型中取, 就不会错行了.


  1. 在模型中添加Image属性
#import 

///  下载的图像
@property (nonatomic, strong) UIImage *image;

2 . 使用MVC更新表格图像

// 判断模型中是否存在图像
if (app.image != nil) {
  NSLog(@"加载模型图像...");
  cell.iconView.image = app.image;
  return cell;
}
// 主线程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    // 设置模型中的图像
    app.image = image;
    // 刷新表格
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];

问题: 如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作
解决办法: 使用”下载操作缓存池”

什么是缓存池? 我们目前接触到得缓存池有:

  • NSSet: 特点无序

UITableViewCell 就是使用NSSet做缓存, 在缓存池中随便娶一个cell复用

  • NSArray: 特点有序, 通过下标Index获取

如果上拉或下拉, 索引就不对啦, 无法标示下载操作

  • NSDictionary: 键值对存储

可以使用图像的URL来做key(URL是唯一的)


分析:
图片加载_第1张图片

实现:

1> 增加一个字典属性作为操作缓存池

 ///  操作缓冲池
@property (nonatomic, strong) NSMutableDictionary *operationCache;

2> 懒加载

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

3> 判断下载操作是否被缓存——正在下载

// 判断操作是否存在
if (self.operationCache[app.icon] != nil) {
    NSLog(@"正在玩命下载中...");
    return cell;
}

4> 将操作添加到操作缓存池

// 2. 将操作添加到操作缓存池
[self.operationCache setObject:downloadOp forKey:app.icon];

// 3. 将下载操作添加到队列
[self.downloadQueue addOperation:downloadOp];

问题:
我们把操作都添加到缓存池中, 没有进行释放, 随着图片数量增加, 会有内存隐患.
解决办法: 下载结束后, 从缓存池清除下载操作


[[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 下载结束,从缓冲池中清除下载操作
            // 提示:NSMutableDictionary 不是线程安全的,因此需要在主线程中删除!可以保证安全!
            // 如果在Image加载出来删除操作, 外部环境是异步执行, 可能两个线程同时删除一个操作, 会导致程序崩溃            
            [self.operationCache removeObjectForKey:app.icon];

            // 将图像保存在模型
            app.image = image;

            // 刷新当前行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];

问题: 小心block中的self会形成循环引用

循环引用分析:
图片加载_第2张图片

[self.operationCache removeObjectForKey:app.icon]; 下载操作结束后便移除, 由此打破了循环引用;


当然此处没有循环引用, 在这里介绍一个解除循环引用的办法:

// 定义 weakSelf,OC中默认都是 strong
__weak typeof(self) weakSelf = self;

应用:

 __weak typeof(self) weakSelf = self;
    NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟延时
        NSLog(@"正在玩命加载中...%@", app.name);
        if (indexPath.row == 0) {
            [NSThread sleepForTimeInterval:10.0];
        }

        NSURL *url = [NSURL URLWithString:app.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [weakSelf.operationCache removeObjectForKey:app.icon];

            // 将图像保存在模型
            app.image = image;            
            // 刷新当前行
            [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];

分析: 在这里使用weakSelf的好处–
当indexPath.row == 0时, 模拟了10″延时;

  • 使用self, 控制器已经pop出去, 但是控制器释放是在10″延时+图片下载+设置图片之后才被销毁;

  • 如果使用weakSelf, 当控制器被pop出去, 控制器会立马销毁, 不会等待图片的操作;

    由此可见, 使用weakSelf更好!


问题: 我们目前采用从模型中获取图片, 但是当图片数量过多如20000, 收到内存警告需要释放图片, 而图片与模型绑定, 不方便释放, 怎么办??

解决办法: 使用图像缓存, 收到内存警告, 直接释放图片缓存池


  • 增加一个图片缓存池属性 NSMutableDictionary
///  图像缓存池
@property (nonatomic, strong) NSMutableDictionary *imageCache;
  • 懒加载
 - (NSMutableDictionary *)imageCache {
    if (_imageCache == nil) {
        _imageCache = [[NSMutableDictionary alloc] init];
    }
    return _imageCache;
}
  • 判断图像缓存池中是否存在图像
// 判断图像缓存池中是否已经存在图像
    if (self.imageCache[app.icon] != nil) {
        NSLog(@"从内存加载图像 %@", app.name);
        cell.iconView.image = self.imageCache[app.icon];
        return cell;
    }
  • 将图片保存到图片缓存池
// 将图像保存在缓存池
[weakSelf.imageCache setObject:image forKey:app.icon];

最后一个问题: 出现内存警告, 清除缓存

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

    // 1. 取消下载操作
    [self.downloadQueue cancelAllOperations];

    // 2. 清空缓冲池
    [self.operationCache removeAllObjects];
    [self.imageCache removeAllObjects];
}

你可能感兴趣的:(网络)