NSOperation是一个抽象类,我们不能直接使用这个类,而是用它的子类。
1、使用NSOperation的子类,我们需要重写main方法,并且在这个方法里面不需要调用父类的main方法。
2、任何操作在执行时,首先会调用start方法,start方法会更新操作的状态(过滤操作);
经过start方法过滤之后,只有正常可执行的操作,就会调用这个main方法
重写操作的入口方法(main方法),就可以在这个方法里面指定操作执行的任务
3、 main方法默认是在子线程异步执行的。
常见属性:
@property (nullable,copy)void (^completionBlock)(void)NS_AVAILABLE(10_6,4_0);
用法:这个block会在完成之后调用。
NSOperation异步下载网络图片
基础代码:
#import "ViewController.h"
#import "AFNetworking.h"
#import "AppInfo.h"
#import "HMTableViewCell.h"
@interfaceViewController ()<UITableViewDataSource>
@property(nonatomic,strong)NSMutableArray<AppInfo*>* appList;
@property (weak,nonatomic)IBOutletUITableView *tableView;
@property(nonatomic,strong)NSOperationQueue* queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.queue=[[NSOperationQueuealloc]init];
[selfloadData];
[self.tableViewregisterNib:[UINibnibWithNibName:@"HMCell"bundle:nil]forCellReuseIdentifier:@"cellId"];
self.tableView.rowHeight=200;
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)loadData
{
AFHTTPSessionManager* manager=[AFHTTPSessionManagermanager];
[manager GET:@"https://raw.githubusercontent.com/qiruihua/image/master/app.json"parameters:nilprogress:nilsuccess:^(NSURLSessionDataTask *_Nonnull task,id _Nullable responseObject) {
NSMutableArray* temp=[NSMutableArrayarray];
for (NSMutableDictionary* infoin responseObject) {
AppInfo* model=[[AppInfoalloc]init];
[model setValuesForKeysWithDictionary:info];
[temp addObject:model];
}
self.appList=temp;
[self.tableViewreloadData];
} failure:nil];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return_appList.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HMTableViewCell* cell=[tableViewdequeueReusableCellWithIdentifier:@"cellId"forIndexPath:indexPath];
AppInfo* model=_appList[indexPath.row];
cell.nameLabel.text=model.name;
cell.downLoadLabel.text=model.download;
//在建立异步下载操作之前,设置占位图
cell.iconView.image=[UIImageimageNamed:@"user_default"];
NSBlockOperation* op=[NSBlockOperationblockOperationWithBlock:^{
NSLog(@"从网络下载");
[NSThreadsleepForTimeInterval:0.1];
NSURL* url=[NSURLURLWithString:model.icon];
NSData* data=[NSDatadataWithContentsOfURL:url];
UIImage *image=[UIImageimageWithData:data];
[[NSOperationQueuemainQueue]addOperationWithBlock:^{
NSLog(@"%@",[NSThreadcurrentThread]);
cell.iconView.image=image;
}];
}];
[self.queueaddOperation:op];
return cell;
}
存在的问题:图片每次展示都要重新去下载,浪费用户流量,解决:实现图片缓存-使用字典做图片内存缓存容器
优化
示例:
#import "ViewController.h"
#import "AFNetworking.h"
#import "AppInfo.h"
#import "HMTableViewCell.h"
@interfaceViewController ()<UITableViewDataSource>
@property(nonatomic,strong)NSMutableArray<AppInfo*>* appList;
@property (weak,nonatomic)IBOutletUITableView *tableView;
@property(nonatomic,strong)NSOperationQueue* queue;
//定义一个字典-缓存池
@property(nonatomic,strong)NSMutableDictionary* imageCache;
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.queue=[[NSOperationQueuealloc]init];
[selfloadData];
[self.tableViewregisterNib:[UINibnibWithNibName:@"HMCell"bundle:nil]forCellReuseIdentifier:@"cellId"];
self.tableView.rowHeight=200;
_imageCache=[[NSMutableDictionaryalloc]init];
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)loadData
{
AFHTTPSessionManager* manager=[AFHTTPSessionManagermanager];
[manager GET:@"https://raw.githubusercontent.com/qiruihua/image/master/app.json"parameters:nilprogress:nilsuccess:^(NSURLSessionDataTask *_Nonnull task,id _Nullable responseObject) {
NSMutableArray* temp=[NSMutableArrayarray];
for (NSMutableDictionary* infoin responseObject) {
AppInfo* model=[[AppInfoalloc]init];
[model setValuesForKeysWithDictionary:info];
[temp addObject:model];
}
self.appList=temp;
[self.tableViewreloadData];
} failure:nil];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return_appList.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HMTableViewCell* cell=[tableViewdequeueReusableCellWithIdentifier:@"cellId"forIndexPath:indexPath];
AppInfo* model=_appList[indexPath.row];
cell.nameLabel.text=model.name;
cell.downLoadLabel.text=model.download;
//返回指定 key的 value,若没有这个 key返回 nil.
UIImage* imageCache=[_imageCacheobjectForKey:model.icon];
if(imageCache!=nil)
{
NSLog(@"内存里有图片,从内存取");
cell.iconView.image=imageCache;
return cell;
}
// //在建立异步下载操作之前,设置占位图
cell.iconView.image=[UIImageimageNamed:@"user_default"];
NSBlockOperation* op=[NSBlockOperationblockOperationWithBlock:^{
NSLog(@"从网络下载");
[NSThreadsleepForTimeInterval:0.1];
NSURL* url=[NSURLURLWithString:model.icon];
NSData* data=[NSDatadataWithContentsOfURL:url];
UIImage *image=[UIImageimageWithData:data];
[[NSOperationQueuemainQueue]addOperationWithBlock:^{
NSLog(@"%@",[NSThreadcurrentThread]);
cell.iconView.image=image;
if (_imageCache!=nil) {
[_imageCachesetObject:imageforKey:model.icon];
}
}];
}];
[self.queueaddOperation:op];
return cell;
}
//问题:当有网络延迟时,有延迟的图片后下载完的,没有延迟的图片先下载完的。在有cell复用的前提下,先下载完的图片先赋值,后下载完的图片
在赋值时会把先下载完的图片覆盖,就造成了图片错行展示。
//解决:刷新对应行,哪一行下载完就只刷新哪一行cell
第二次优化:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HMTableViewCell* cell=[tableViewdequeueReusableCellWithIdentifier:@"cellId"forIndexPath:indexPath];
AppInfo* model=_appList[indexPath.row];
cell.nameLabel.text=model.name;
cell.downLoadLabel.text=model.download;
UIImage* memImage=[_imageCacheobjectForKey:model.icon];
if (memImage!=nil) {
NSLog(@"从内存中加载...%@",model.name);
cell.iconView.image=memImage;
return cell;
}
//在建立异步下载操作之前,设置占位图
cell.iconView.image=[UIImageimageNamed:@"user_default"];
NSBlockOperation* op=[NSBlockOperationblockOperationWithBlock:^{
NSLog(@"从网络下载");
//模拟网络延迟:让屏幕之外的图片下载时有严重的延迟,仅仅是为了放大BUG的效果而已
if(indexPath.row>9)
{
[NSThreadsleepForTimeInterval:5.0];
}
NSURL* url=[NSURLURLWithString:model.icon];
NSData* data=[NSDatadataWithContentsOfURL:url];
UIImage *image=[UIImageimageWithData:data];
//回到主线程给iconImageView赋值
[[NSOperationQueuemainQueue]addOperationWithBlock:^{
NSLog(@"%@",[NSThreadcurrentThread]);
// cell.iconView.image=image;
if (image!=nil) {
[_imageCachesetObject:imageforKey:model.icon];
[self.tableViewreloadRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationNone];
}
}];
}];
[self.queueaddOperation:op];
return cell;
}
//问题:
当在10s之类快速的上下滚动列表时,会出现多个下载操作下载同一张图片的问题
分析:
1、每次展示一张图片,就会建立对应的下载操作去下载cell上的图片
2、但是,当有网络延迟时,如果这个cell上的图片还没有下载完成,那么这个cell每出现
一次就会建立一个下载操作去重复的下载同一张图片
解决:操作缓存池-类似图片缓存池
1、在建立下载操作之前先判断这个图片对应的下载操作有没有,如果有就不再建立下载操作,
反之,就建立新的下载操作去下载这个图片
第三次优化:
#import "ViewController.h"
#import "AFNetworking.h"
#import "AppInfo.h"
#import "HMTableViewCell.h"
@interfaceViewController ()<UITableViewDataSource>
@property(nonatomic,strong)NSMutableArray<AppInfo*>* appList;
@property (weak,nonatomic)IBOutletUITableView *tableView;
@property(nonatomic,strong)NSOperationQueue* queue;
@property(nonatomic,strong)NSMutableDictionary* imageCache;
@end
@implementation ViewController
{
NSMutableDictionary* _opCache;
}
- (void)viewDidLoad {
[superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.queue=[[NSOperationQueuealloc]init];
[selfloadData];
[self.tableViewregisterNib:[UINibnibWithNibName:@"HMCell"bundle:nil]forCellReuseIdentifier:@"cellId"];
_imageCache =[[NSMutableDictionaryalloc]init];
_opCache=[[NSMutableDictionaryalloc]init];
self.tableView.rowHeight=200;
}
//处理内存警告
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[_imageCacheremoveAllObjects];
[_opCacheremoveAllObjects];
[_queuecancelAllOperations];
}
-(void)loadData
{
AFHTTPSessionManager* manager=[AFHTTPSessionManagermanager];
[manager GET:@"https://raw.githubusercontent.com/qiruihua/image/master/app.json"parameters:nilprogress:nilsuccess:^(NSURLSessionDataTask *_Nonnull task,id _Nullable responseObject) {
NSMutableArray* temp=[NSMutableArrayarray];
for (NSMutableDictionary* infoin responseObject) {
AppInfo* model=[[AppInfoalloc]init];
[model setValuesForKeysWithDictionary:info];
[temp addObject:model];
}
self.appList=temp;
[self.tableViewreloadData];
} failure:nil];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return_appList.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HMTableViewCell* cell=[tableViewdequeueReusableCellWithIdentifier:@"cellId"forIndexPath:indexPath];
AppInfo* model=_appList[indexPath.row];
cell.nameLabel.text=model.name;
cell.downLoadLabel.text=model.download;
UIImage* memImage=[_imageCacheobjectForKey:model.icon];
if (memImage!=nil) {
NSLog(@"从内存中加载...%@",model.name);
cell.iconView.image=memImage;
return cell;
}
//在建立下载操作之前,判断要建立的下载操作在缓存池有没有
if ([_opCacheobjectForKey:model.icon]) {
NSLog(@"正在下载...%@",model.name);
return cell;
}
//在建立异步下载操作之前,设置占位图
cell.iconView.image=[UIImageimageNamed:@"user_default"];
NSBlockOperation* op=[NSBlockOperationblockOperationWithBlock:^{
NSLog(@"从网络下载");
//模拟网络延迟:让屏幕之外的图片下载时有严重的延迟,仅仅是为了放大BUG的效果而已
if(indexPath.row>9)
{
[NSThreadsleepForTimeInterval:5.0];
}
NSURL* url=[NSURLURLWithString:model.icon];
NSData* data=[NSDatadataWithContentsOfURL:url];
UIImage *image=[UIImageimageWithData:data];
//回到主线程给iconImageView赋值
[[NSOperationQueuemainQueue]addOperationWithBlock:^{
NSLog(@"%@",[NSThreadcurrentThread]);
// cell.iconView.image=image;
if (image!=nil) {
[_imageCachesetObject:imageforKey:model.icon];
[self.tableViewreloadRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationNone];
}
//图片下载完成,把操作从操作缓存池移除
[_opCacheremoveObjectForKey:model.icon];
}];
}];
//把下载操作添加到操作缓存池
[_opCachesetObject:opforKey:model.icon];
[self.queueaddOperation:op];
return cell;
}
//问题:当程序再次启动时,内存缓存失效,图片依然需要重新下载
// 解决:沙盒缓存
第四次优化
准备分类:
#import <Foundation/Foundation.h>
@interface NSString (path)
//获取cache文件缓存路径
-(NSString*)appendCachePath;
@end
----------------#import "NSString+path.h"
@implementation NSString (path)
-(NSString*)appendCachePath
{
NSString* path=NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES).lastObject;
NSString* name=[selflastPathComponent];
NSString* filePath=[pathstringByAppendingPathComponent:name];
return filePath;
}
@end
-------------
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HMTableViewCell* cell=[tableViewdequeueReusableCellWithIdentifier:@"cellId"forIndexPath:indexPath];
AppInfo* model=_appList[indexPath.row];
cell.nameLabel.text=model.name;
cell.downLoadLabel.text=model.download;
UIImage* memImage=[_imageCacheobjectForKey:model.icon];
if (memImage!=nil) {
NSLog(@"从内存中加载...%@",model.name);
cell.iconView.image=memImage;
return cell;
}
//在建立操作之前,内存缓存判断之后,判断有没有沙盒缓存
UIImage* cacheImage=[UIImageimageWithContentsOfFile:[model.iconappendCachePath]];
if (cacheImage!=nil) {
NSLog(@"从沙盒中加载... %@",model.name);
[_imageCachesetObject:cacheImageforKey:model.icon];
cell.iconView.image=cacheImage;
return cell;
}
//在建立下载操作之前,判断要建立的下载操作在缓存池有没有
if ([_opCacheobjectForKey:model.icon]) {
NSLog(@"正在下载...%@",model.name);
return cell;
}
//在建立异步下载操作之前,设置占位图
cell.iconView.image=[UIImageimageNamed:@"user_default"];
NSBlockOperation* op=[NSBlockOperationblockOperationWithBlock:^{
NSLog(@"从网络下载");
//模拟网络延迟:让屏幕之外的图片下载时有严重的延迟,仅仅是为了放大BUG的效果而已
if(indexPath.row>9)
{
[NSThreadsleepForTimeInterval:5.0];
}
NSURL* url=[NSURLURLWithString:model.icon];
NSData* data=[NSDatadataWithContentsOfURL:url];
UIImage *image=[UIImageimageWithData:data];
if (image) {
//将图片保存到内存缓存池
[data writeToFile:[model.iconappendCachePath]atomically:YES];
}
//回到主线程给iconImageView赋值
[[NSOperationQueuemainQueue]addOperationWithBlock:^{
NSLog(@"%@",[NSThreadcurrentThread]);
// cell.iconView.image=image;
if (image!=nil) {
[_imageCachesetObject:imageforKey:model.icon];
[self.tableViewreloadRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationNone];
}
//图片下载完成,把操作从操作缓存池移除
[_opCacheremoveObjectForKey:model.icon];
}];
}];
//把下载操作添加到操作缓存池
[_opCachesetObject:opforKey:model.icon];
[self.queueaddOperation:op];
return cell;
}