本文主要介绍通过自定义NSOperation实现图片异步下载功能。下面直接上代码。
1. 先把程序的框架搭建起来:
window的主视图式导航栏控制器UINavigationController,导航栏控制器UINavigationController的根视图是UITableView。效果如下:
2. 向UITableView上设置数据,这里加载的是plist文件:
3. 将plist文件中的数据转化成模型,创建模型HXApp。
HXApp.h
#import <Foundation/Foundation.h>
@interface HXApp : NSObject
/**
* 应用名称
*/
@property (nonatomic, copy) NSString *name;
/**
* 应用图标
*/
@property (nonatomic, copy) NSString *icon;
/**
* 应用下载量
*/
@property (nonatomic, copy) NSString *download;
+ (instancetype)appWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
HXApp.m
#import "HXApp.h"
@implementation HXApp
+ (instancetype)appWithDict:(NSDictionary *)dict {
return [[self alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict {
HXApp *app = [[HXApp alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
}
@end
4. 在控制器中读取plist文件数据,创建一个模型数组apps(懒加载),转化成模型后添加到数组中;
- (NSMutableArray *)apps {
if (_apps == nil) {
// 读取数据
NSArray *appArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
// 将数据中的字典转化成模型
NSMutableArray *apps = [NSMutableArray array];
for (NSDictionary *appDict in appArray) {
HXApp *app = [HXApp appWithDict:appDict];
[apps addObject:app];
}
_apps = apps;
}
return _apps;
}
5. 实现tableView的数据源方法:
#pragma mark - tableView datasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 创建cell
static NSString *identifier = @"APP";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
// 设置数据
HXApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
<span style="color:#FF0000;">// 显示图片(重点介绍该部分)</span>
return cell;
}
6. 下载图片,我们通过自定义的NSOperation(HXDownLoadOperation)来完成,我们让HXDownLoadOperation专门用来下载图片。HXDownLoadOperation要先下载图片就得知道url,又因为它是下载cell中的图片,还要知道是哪个cell的图片。
HXDownLoadOperation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface HXDownLoadOperation : NSOperation
/**
* 下载路径
*/
@property (nonatomic, copy) NSString *url;
/**
* 下载的图片索引
*/
@property (nonatomic, strong) NSIndexPath *indexPath;
@end
7. 在cellForRowAtIndexPath:方法中添加下面代码,并有思路:
<span style="color:#FF0000;">// 显示图片</span><span style="color:#FF0000;">(重点介绍该部分)</span>
// 1.根据图片url,向字典images中取出对应的image
UIImage *image = [self.urlsWithImages valueForKey:app.icon];
// 2.判断image是否存在
if (image) {// image存在
cell.imageView.image = image;
}else {// image不存在
// 占位图片
cell.imageView.image = [UIImage imageNamed:@"000"];
// 3.下载图片
// 根据图片url,向字典operations中取出对应的操作operation
HXDownLoadOperation *operation = [self.urlsWithOperations valueForKey:app.icon];
// 4.判断operation下载操作是否存在(即是否正在下载该图片)
if (operation) {// operation下载操作存在(即正在下载该图片,不作任何处理)
}else {// operation下载操作不存在(即还没有下载过该图片)
// 创建下载操作
operation = [[HXDownLoadOperation alloc] init];
// 图片url
operation.url = app.icon;
operation.delegate = self;
operation.indexPath = indexPath;
// 添加到队列中(异步执行)
[self.queue addOperation:operation];// 调用该方法会执行start方法(异步),在start方法中会调用队列的main方法
// 将新的下载操作添加到字典operations中
// self.urlsWithOperations[app.icon] = operation;
[self.urlsWithOperations setValue:operation forKey:app.icon];
}
}
8. HXDownLoadOperation知道图片的url和该图片在cell上的位置后,重写main方法,在main方法中下载完图片,要通知cell显示图片,所在此时的HXDownLoadOperation.h文件中:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class HXDownLoadOperation;
@protocol HXDownLoadOperationDelegate <NSObject>
@optional
/**
* 执行完系在操作后,会调用该代理方法
*
* @param operation 下载操作
* @param image 下载的图片
*/
- (void)downLoadOperation:(HXDownLoadOperation *)operation didFinishedDownLoad:(UIImage *)image;
@end
@interface HXDownLoadOperation : NSOperation
@property (nonatomic, weak) id<HXDownLoadOperationDelegate> delegate;
/**
* 下载路径
*/
@property (nonatomic, copy) NSString *url;
/**
* 下载的图片索引
*/
@property (nonatomic, strong) NSIndexPath *indexPath;
@end
HXDownLoadOperation.m
/**
* 重写main方法,执行下载的操作
*/
- (void)main {
NSURL *downLoadUrl = [NSURL URLWithString:self.url];
NSData *imageData = [NSData dataWithContentsOfURL:downLoadUrl];
UIImage *image = [UIImage imageWithData:imageData];
// 下载完成后,通知代理
if ([self.delegate respondsToSelector:@selector(downLoadOperation:didFinishedDownLoad:)]) {
// 在主线程中通知代理
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate downLoadOperation:self didFinishedDownLoad:image];
});
}
}
9. 将控制器设置为HXDownLoadOperation的代理,并实现代理方法:
#pragma mark - HXDownLoadOperationDelegate
- (void)downLoadOperation:(HXDownLoadOperation *)operation didFinishedDownLoad:(UIImage *)image {
// 执行完下载操作,将该下载操作从字典operations中移除(防止下载失败等问题)
[self.urlsWithOperations removeObjectForKey:operation.url];
// 向字典images中添加下载之后的图片
if (image) {// 该判断是防止图片路径无效等问题
self.urlsWithImages[operation.url] = image;
// 刷新表格(局部刷新)
[self.tableView reloadRowsAtIndexPaths:@[operation.indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}
10. 到此图片下载的步骤已经基本完成,下载完的图片不会在重新下载,所以在滚动cell的时候不会在去下载之前的图片。现在对这个项目做一个小优化。
// 细节优化(当滚动表格的时候,停止下载图片,不滚动的时候,再继续下载)
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
// 当开始拖拽的时候,通知cell停止下载图片
[self.queue setSuspended:YES];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
// 当停止拖拽的时候,通知cell继续下载图片
[self.queue setSuspended:NO];
}
最后运行看一下效果:
本文的内容介绍完了,主要是想总结一下自定义NSOperation的步骤,和实现图片下载的思路。其实关于图片下载有一个很牛X的第三方类库:SDWebImage。