本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
上一节,我对NSOperation的基本概念及使用进行了介绍,想要了解的,请点击这里。本节中,我介绍自定义NSOperation实现多线程异步下载图片,类似于SDWebImage。
自定义NSOperation的步骤很简单,重写 - (void)main方法,在里面实现想执行的任务。
重写 - (void)main方法注意点:
1.自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
2.经常通过 - (BOOL)isCancelled 方法检测操作是否被取消,对取消做出响应。
Demo最终的效果图:
上面的图片均是通过NSOperation异步下载的。
下面,我具体介绍代码的实现过程。
1.首先,我准备了数据源(data.plist文件)
2. 定义了对应的数据模型
@interface DataModel : NSObject
/* 图片名称 */
@property (nonatomic,copy) NSString *name;
/* 图片url地址 */
@property (nonatomic,copy) NSString *url;
/* 下载次数 */
@property (nonatomic,strong) NSNumber *downloadedCount;
// 字典转化为模型
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
@implementation DataModel
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [super init]) {
// (KVC)字典转模型
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
@end
3. ViewController中得代码:
#import "ViewController.h"
#import "DataModel.h"
#import "LFDownloadOperation.h"
@interface ViewController ()<LFDownloadOperationDelegate>
/* 数据源 */
@property (nonatomic,strong) NSMutableArray *dataLists;
// Key:url, Value: UIImage (存放图片的缓存字典,有的话,直接取出使用;没有的话,下载)
@property (nonatomic,strong) NSMutableDictionary *images;
// Key:url, Value: LFDownloadOperation
@property (nonatomic,strong) NSMutableDictionary *operations;
// 下载队列,存放LFDownloadOperation
@property (nonatomic,strong) NSOperationQueue *queue;
@end
@implementation ViewController
#pragma mark - Lazy Load
- (NSMutableArray *)dataLists {
if (!_dataLists) {
NSString *file = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"plist"];
NSArray *dataArr = [NSArray arrayWithContentsOfFile:file];
NSMutableArray *tempLists = [NSMutableArray array];
for (NSDictionary *dict in dataArr) {
DataModel *model = [[DataModel alloc] initWithDict:dict];
[tempLists addObject:model];
}
_dataLists = tempLists;
}
return _dataLists;
}
- (NSMutableDictionary *)images {
if (!_images) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
- (NSMutableDictionary *)operations {
if( !_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark - UITableView Delegate/DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataLists.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifer = @"UITableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifer];
}
DataModel *model = self.dataLists[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = [model.downloadedCount stringValue];
// 主要实现下载的代码
UIImage *image = self.images[model.url];
if (image) { // 图片下载完毕(在字典中)
cell.imageView.image = image;
} else {
// 创建下载Operation并设置默认占位图片
LFDownloadOperation *operation = self.operations[model.url];
cell.imageView.image = [UIImage imageNamed:@"placeholder.jpg"];
// 如果operation有值,说明正在下载
if (!operation) {
// 还没有下载,立刻创建operation并下载
operation = [[LFDownloadOperation alloc] init];
operation.delegate = self;
operation.url = model.url;
operation.indexPath = indexPath;
// 将每张图片的下载地址和每个opearion建立对应关系
self.operations[model.url] = operation;
// 不能在这里创建队列,应该只创建一个队列(所以使用懒加载)
//NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 加到队列中并下载
[self.queue addOperation:operation];
}
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 130;
}
#pragma mark - LFDownloadOperationDelegate
- (void)downloadOperation:(LFDownloadOperation *)operation didFinishedWithImage:(UIImage *)image {
NSIndexPath *indexPath = operation.indexPath;
DataModel *model = self.dataLists[indexPath.row];
// 当一张图片下载完成了,则应该移除这张图片对应的operation
[self.operations removeObjectForKey:model.url];
// 将下载完成的图片保存到images这个字典缓存池中
self.images[model.url] = image;
// 每次有图片下载完成,则刷新对应的cell并显示图片
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
@end
4. 自定义的NSOperation
@class LFDownloadOperation;
@protocol LFDownloadOperationDelegate <NSObject>
@optional
/* 代理返回下载完毕的图片 */
- (void)downloadOperation:(LFDownloadOperation *)operation didFinishedWithImage:(UIImage *)image;
@end
@interface LFDownloadOperation : NSOperation
@property (nonatomic,copy) NSString *url;
@property (nonatomic,strong) NSIndexPath *indexPath;
@property (nonatomic,assign) id<LFDownloadOperationDelegate> delegate;
@end
@implementation LFDownloadOperation
// NSOperation中的main就是实现相应的异步操作,所以重写父类方法
- (void)main
{
// 因为LFDownloadOperation已经不在主线程的自动释放池了,所以要包含在@autoreleasepool中
@autoreleasepool {
// 取消操作发生在任何时刻都有可能,因此在执行任何操作之前,先检测该操作是否已经被取消
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:self.url];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (self.isCancelled) {
return;
}
if ([self.delegate respondsToSelector:@selector(downloadOperation:didFinishedWithImage:)]){
dispatch_async(dispatch_get_main_queue(), ^{
// 最后将异步下载完成的图片返回到主线程(dispatch_get_main_queue)
[self.delegate downloadOperation:self didFinishedWithImage:image];
});
}
}
}
@end