NSOperation下载图片

下载多张图片

假设需求,有一个cell表格,中间有图片,label等数据都是需要联网下载

首先实现下载功能

JXModel.h

#import <Foundation/Foundation.h>

@interface JXModel : NSObject
/** 图片 */
@property (nonatomic,strong) NSString * icon;
/** 名字 */
@property (nonatomic,strong) NSString * name;
/** 下载量 */
@property (nonatomic,strong) NSString * download;

+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end

JXModel.m

#import "JXModel.h"

@implementation JXModel
+ (instancetype)modelWithDict:(NSDictionary *)dict {
    JXModel * model = [[self alloc] init];

    [model setValuesForKeysWithDictionary:dict];

    return model;
}
@end

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UITableViewController


@end

ViewController.m

#import "ViewController.h"
#import "JXModel.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [NSArray array];

        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];

        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            [items addObject:[JXModel modelWithDict:dict]];
        }

        _items = items;

    }
    return _items;
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";

    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    JXModel * item = self.items[indexPath.row];

    NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
    UIImage * image = [UIImage imageWithData:data];
    cell.imageView.image = image;

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    return cell;
}
@end

第一次页面效果

这是第一次页面效果,表面上看上去好像是全部实现功能,但是会有问题

问题1:首先,当我滑动的时候,会出现卡顿现象

问题2:每次滑动或者进入的时候都是需要网络来下载

问题3:当网络比较慢的时候,用户不操作页面,就会一直不会显示图片

问题3:当网络比较慢的时候,用户已经操作,就会出现图片跟标题错乱的情况

问题解决:

  • 问题1:滑动会出现卡顿的现象是因为每次滑动都会重新下载图片,而且下载是在主线程上下载,所以当网络慢的时候回出现卡顿现象
  • 问题2:每次图片都是网络请求下载(重复下载问题)
  • 问题3:因为是使用苹果自带的cell,当创建的时候图片不存在,这时候即使图片已经下载下来了,这时候同样是图片不能显示出来,图片虽然已经在内存中,但是尺寸为0
  • 问题4:网络比较慢的时候,虽然已经在下载,但是用户在滑动,有可能已经在下载但是已近刚进入到缓存池中,所以下载完成后会复用,显示在别的位置

解决页面卡顿

思路:页面卡顿主要是由于每次滑动cell都重新下载图片,解决办法就是定义一个可变字典,将下载好的图片保存到内存中(key为其路径),每次滑动的时候首先从字典中取出图片,如果没有图片就执行下载功能如果有就直接将图片放到imageView上

#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果有图片

        cell.imageView.image = image;
    } else { // 如果没有图片

        // 将头像下载成data模型
        NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
        image = [UIImage imageWithData:data];
        // 将下载的图片保存到缓存中
        self.imageCache[item.icon] = image;
    }

    return cell;
}
@end

解决每次重新打开APP就会下载图片

思路:将每次请求下来的图片不仅保存到内存中,还要保存到沙盒中,这样每次启动的时候就先从内存中取出图片,如果没有就到沙盒中取图片,如果还是没有就会下载图片

#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果内存中有图片

        cell.imageView.image = image;
    } else { // 如果内存中没有图片
        // 缓存路径
        NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 文件名
        NSString * name = [item.icon lastPathComponent];
        // 拼接成一个完整路径
        NSString * file = [cachesPath stringByAppendingPathComponent:name];
        NSLog(@"file==%@",file);
        // 取出沙盒中数据
        NSData * data = [NSData dataWithContentsOfFile:file];
        if (data) { // 沙盒中有图片
            image = [UIImage imageWithData:data];
            // 将下载的图片保存到缓存中
            self.imageCache[item.icon] = image;
        } else { // 沙盒中没有图片,下载图片
            // 将头像下载成data模型
            NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
            cell.imageView.image = [UIImage imageWithData:data];
            // 将图片写到沙盒中
            // 将文件写入到沙盒中
            [data writeToFile:file atomically:YES];
        }

    }

    return cell;
}
@end

解决在主线程执行耗时操作

思路:将下载图片,以及从沙盒中读取文件放到子线程中,因为会可能比较耗时。

#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
/** 队列对象 */
@property (nonatomic,strong) NSOperationQueue * queue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}

- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
    }
    return _queue;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果内存中有图片

        cell.imageView.image = image;
    } else { // 如果内存中没有图片
        // 缓存路径
        NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 文件名
        NSString * name = [item.icon lastPathComponent];
        // 拼接成一个完整路径
        NSString * file = [cachesPath stringByAppendingPathComponent:name];
        // 取出沙盒中数据
        NSData * data = [NSData dataWithContentsOfFile:file];
        if (data) { // 沙盒中有图片
            image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            // 将下载的图片保存到缓存中
            self.imageCache[item.icon] = image;
        } else { // 沙盒中没有图片,下载图片

            // 下载放到子线程中
            [self.queue addOperationWithBlock:^{
                // 将头像下载成data模型
                NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
                UIImage * image = [UIImage imageWithData:data];
                // 将显示图片放到主线程中
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                    cell.imageView.image = image;
                }];

                // 将图片写到沙盒中
                // 将文件写入到沙盒中
                [data writeToFile:file atomically:YES];
            }];

        }

    }

    return cell;
}
@end

看似上面的代码已经实现我们所需要的功能,但是还是存在不少问题的

问题:当我们在子线程中下载图片的时候,让线程sleep一秒钟,就会有问题

问题描述:假设我们下载一个图片需要两秒钟,但是我们在一秒的时候我们来回滚动cell,这时候就会出现图片显示错乱的问题

睡眠线程问题

解释:问题产生的原因当下载图片需要比较长时间的时候,在下载的过程中用户不断滚动,因为图片没有下载完成,所以沙盒以及内存中不可能存在图片,所以图片就会不断重复下载。 还有一个问题就是数据错乱:因为在下载的过程中,图片没有下载完,cell就已经进入了缓存池,当刚好开始复用的时候图片下载完成所以就会出现数据错乱现象

解决数据错乱现象

保证一个图片只有一个下载路径:因为数据重复下载就是因为当一个图片没有下载完成就已经进入了缓存池中,这是内存以及沙盒中都不存在,下次进来的时候还是要继续下载

解决了重复下载问题:每次进入下载的时候都要总内存中取出缓存的操作,

#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
/** 队列对象 */
@property (nonatomic,strong) NSOperationQueue * queue;
/** 所有的操作对象 */
@property (nonatomic,strong) NSMutableDictionary * operation;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}

- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
    }
    return _queue;
}
- (NSMutableDictionary *)operation{
    if (_operation == nil) {
        _operation = [NSMutableDictionary dictionary];
    }
    return _operation;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果内存中有图片

        cell.imageView.image = image;
    } else { // 如果内存中没有图片
        // 缓存路径
        NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 文件名
        NSString * name = [item.icon lastPathComponent];
        // 拼接成一个完整路径
        NSString * file = [cachesPath stringByAppendingPathComponent:name];
        // 取出沙盒中数据
        NSData * data = [NSData dataWithContentsOfFile:file];
        if (data) { // 沙盒中有图片
            image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            // 将下载的图片保存到缓存中
            self.imageCache[item.icon] = image;
        } else { // 沙盒中没有图片,下载图片
            NSOperation * operation = self.operation[item.icon];
            if (operation == nil) { // 如果这张图没有下载任务
                NSLog(@"【瞎子啊任务】");
                operation = [NSBlockOperation blockOperationWithBlock:^{
                    // 下载放到子线程中
                    // 将头像下载成data模型
                    NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
                    UIImage * image = [UIImage imageWithData:data];

                    // 将显示图片放到主线程中
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                        cell.imageView.image = image;
                    }];

                    // 将图片写到沙盒中
                    // 将文件写入到沙盒中
                    [data writeToFile:file atomically:YES];
                    self.imageCache[item.icon] = image;
                }];

                // 添加到队列中
                [self.queue addOperation:operation];
                // 将操作队列添加到操作字典中
                self.operation[item.icon] = operation;
            }

        }

    }

    return cell;
}
@end

解决数据错乱问题:不管imageView复用到哪里,我们只需要每次下载完成之后刷新imageView之前的行

刷新行之后还是会执行

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
/** 队列对象 */
@property (nonatomic,strong) NSOperationQueue * queue;
/** 所有的操作对象 */
@property (nonatomic,strong) NSMutableDictionary * operation;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}

- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
    }
    return _queue;
}
- (NSMutableDictionary *)operation{
    if (_operation == nil) {
        _operation = [NSMutableDictionary dictionary];
    }
    return _operation;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果内存中有图片

        cell.imageView.image = image;
    } else { // 如果内存中没有图片
        // 缓存路径
        NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 文件名
        NSString * name = [item.icon lastPathComponent];
        // 拼接成一个完整路径
        NSString * file = [cachesPath stringByAppendingPathComponent:name];
        // 取出沙盒中数据
        NSData * data = [NSData dataWithContentsOfFile:file];
        if (data) { // 沙盒中有图片
            image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            // 将下载的图片保存到缓存中
            self.imageCache[item.icon] = image;
        } else { // 沙盒中没有图片,下载图片
            NSOperation * operation = self.operation[item.icon];
            if (operation == nil) { // 如果这张图没有下载任务
                operation = [NSBlockOperation blockOperationWithBlock:^{
                    // 下载放到子线程中
                    // 将头像下载成data模型
                    NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
                    UIImage * image = [UIImage imageWithData:data];

                    // 将显示图片放到主线程中
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                    }];

                    // 将图片写到沙盒中
                    // 将文件写入到沙盒中
                    [data writeToFile:file atomically:YES];
                    self.imageCache[item.icon] = image;
                }];

                // 添加到队列中
                [self.queue addOperation:operation];
                // 将操作队列添加到操作字典中
                self.operation[item.icon] = operation;
            }

        }

    }

    return cell;
}
@end

这个代码还是会有问题:问题之一就是下载图片的时候,当我哦们网络下载超时的时候,这时候data数据就是nil

 // 将头像下载成data模型
   NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];
   UIImage * image = [UIImage imageWithData:data];

   // 将显示图片放到主线程中
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{

    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }];

   // 将图片写到沙盒中
   // 将文件写入到沙盒中
   [data writeToFile:file atomically:YES];
   self.imageCache[item.icon] = image;

    当data为nil,这时候image同样为nil,那么代码执行到self.imageCache[item.icon] = image,的结果就是self.imageCache[item.icon] = nil,但是字典中不能存nil,所以就会报错
#import "ViewController.h"
#import "JXItem.h"
@interface ViewController ()
/** 模型数组 */
@property (nonatomic,strong) NSArray * items;
/** 缓存图片 */
@property (nonatomic,strong) NSMutableDictionary * imageCache;
/** 队列对象 */
@property (nonatomic,strong) NSOperationQueue * queue;
/** 所有的操作对象 */
@property (nonatomic,strong) NSMutableDictionary * operation;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - 懒加载
- (NSArray *)items {
    if (_items == nil) {
        _items = [[NSArray alloc] init];

        // 因为是本地的,所以从数组中加载模型
        NSString * path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray * arrayDict = [NSArray arrayWithContentsOfFile:path];
        // 定义数组,存储读取的数据模型
        NSMutableArray * items = [NSMutableArray array];
        for (NSDictionary * dict in arrayDict) {
            JXItem * item = [JXItem itemWithDict:dict];
            [items addObject:item];
        }
        _items = items;

    }
    return _items;
}
- (NSMutableDictionary *)imageCache{
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionary];
    }
    return _imageCache;
}

- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 3;
    }
    return _queue;
}
- (NSMutableDictionary *)operation{
    if (_operation == nil) {
        _operation = [NSMutableDictionary dictionary];
    }
    return _operation;
}
#pragma mark - UITableViewDelegate && DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString * identifier = @"app";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    // 取出数据模型
    JXItem * item = self.items[indexPath.row];

    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;

    // 先从内存缓存中取出图片
    UIImage * image = self.imageCache[item.icon];
    if (image) { // 如果内存中有图片

        cell.imageView.image = image;
    } else { // 如果内存中没有图片
        // 缓存路径
        NSString * cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 文件名
        NSString * name = [item.icon lastPathComponent];
        // 拼接成一个完整路径
        NSString * file = [cachesPath stringByAppendingPathComponent:name];
        // 取出沙盒中数据
        NSData * data = [NSData dataWithContentsOfFile:file];
        if (data) { // 沙盒中有图片
            image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            // 将下载的图片保存到缓存中
            self.imageCache[item.icon] = image;
        } else { // 沙盒中没有图片,下载图片
            NSOperation * operation = self.operation[item.icon];
            if (operation == nil) { // 如果这张图没有下载任务
                operation = [NSBlockOperation blockOperationWithBlock:^{
                    // 下载放到子线程中
                    // 将头像下载成data模型
                    NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.icon]];

                    // 数据下载失败 (意味着内存中没有,沙盒中也没有,)当下次来到这个地方的时候就会重复下载,但是因为队列已经添加到了队列数组中,还是会重复执行
                    if (data == nil) {
                        [self.operation removeObjectForKey:item.icon];
                        return ;
                    }
                    UIImage * image = [UIImage imageWithData:data];

                    // 将显示图片放到主线程中
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                    }];

                    // 将图片写到沙盒中
                    // 将文件写入到沙盒中
                    [data writeToFile:file atomically:YES];
                    self.imageCache[item.icon] = image;
                }];

                // 添加到队列中
                [self.queue addOperation:operation];
                // 将操作队列添加到操作字典中
                self.operation[item.icon] = operation;

                // 下载完成后将下载操作移除
                [self.operation removeObjectForKey:item.icon];
            }

        }

    }

    return cell;
}
@end

results matching ""

    No results matching ""