NSOperation的那些事
2017-01-12 本文已影响1233人
AKyS佐毅
自定义operation
custom_operation.png根据文档,每一个operation都必须实现一下方法:
- 继承NSOperation类,重写初始化方法。
- 重写main方法。
具体步骤如下:
#import <Foundation/Foundation.h>
@interface DownloadOperation : NSOperation
@property (nonatomic,copy,readonly) NSString *downloadMark;
- (instancetype)initWithDownLoadMark:(NSString *)downloadMark;
@end
#import "DownloadOperation.h"
@interface DownloadOperation ()
//SDK 中为了不让外部的类修改,往往采用.h声明为readonly ,.m声明为readwrite的方式,在内部使用。
@property (nonatomic,copy,readwrite) NSString *downloadMark;
@end
@implementation DownloadOperation
- (instancetype)initWithDownLoadMark:(NSString *)downloadMark{
if (self == [super init]) {
self.downloadMark = downloadMark;
}
return self;
}
// 必须实现main函数
- (void)main{
@autoreleasepool {
for (int i = 0; i< 10000; i++) {
if (self.isCancelled) {
break;
}
NSLog(@">>> %@ 当前运行到 %d 个任务",self.downloadMark,i);
}
}
}
@end
//下载操作
DownloadOperation *operation1 = [[DownloadOperation alloc]initWithDownLoadMark:@"downloadOperation1"];
DownloadOperation *operation2 = [[DownloadOperation alloc]initWithDownLoadMark:@"downloadOperation2"];
//将所有的任务放在操作队列中
NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
operationQueue.name = @"downloadOperationQueue";
operationQueue.maxConcurrentOperationCount = 100;
[operation1 addDependency:operation2];
[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
// [operationQueue addOperation:operation2]; 重复添加operation,程序会崩溃。无法重复添加相同的操作队列,只能重新初始化操作队列。
NSArray *allOperation = [operationQueue operations];
NSLog(@">>>> 当前所有操作队列的集合是:%@",allOperation);
//每个操作都有自己的完成回调
[operation1 setCompletionBlock:^{
NSLog(@">>>>>> downloadOperation1 completion");
}];
[operation2 setCompletionBlock:^{
NSLog(@">>>>>> downloadOperation2 completion");
}];
// 取消某个操作任务
[self execute:^{
[operation1 cancel];
} afterDelay:1.0f];
//这个方法会挂起或者恢复一个执行的任务.挂起一个队列可以阻止该队列中没有开始的任务.换句话说,在任务队列中还没有开始执行的任务是会被挂起的,直到这个挂起操作被恢复.
[self execute:^{
//此时operation1还没有执行, 所以将会被挂起。 而已经执行的operation2将会继续执行,直至结束。
[operationQueue setSuspended:YES];
} afterDelay:0.3f];
//恢复所有被挂起的操作
[self execute:^{
[operationQueue setSuspended:NO];
} afterDelay:4.0f];
//取消所有队列任务
[self execute:^{
/*
要取消一个队列中的所有操作,只要调用“cancelAllOperations”方法即可。还记得之前提醒过经常检查NSOperation中的isCancelled属性吗?
原因是“cancelAllOperations”并没有做太多的工作,他只是对队列中的每一个操作调用“cancel”方法 — 这并没有起很大作用!:] 如果一个操作并没有开始,然后你对它调用“cancel”方法,操作会被取消,并从队列中移除。然而,如果一个操作已经在执行了,这就要由单独的操作去识 别撤销(通过检查isCancelled属性)然后停止它所做的工作。
*/
[operationQueue cancelAllOperations];
} afterDelay:5.0f];
- (void)execute:(dispatch_block_t)block afterDelay:(int64_t)time{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), block);
}
需要注意的点:
- 1:如果需要取消某个操作任务,可以调用[operation cancle] 方法。同时需要在重写的main函数中,做相应的移除,终止操作。
- 2: 一旦操作提交到线程操作队列中,就不能设置某个操作任务的依赖属性等等。比如上边例子中** [operation1 addDependency:operation2];** 放在 ** [operationQueue addOperation:operation1]**; 之前 和之后是完全不同的。放在加入操作队列之前还是能够起作用,放在之后,就不起任何作用。所以别放错位置。
- 3: 取消了一个操作,它不会马上就发生。它会在未来的某个时候某人在main函数中明确地检查**isCancelled == YES **时被取消掉;否则,操作会一直执行到完成为止。
这样就会导致一个问题,可能这个操作任务在你调用取消函数之前就返回了,所以看到的情况就是我明明对该操作做了取消,为什么还有返回数据的原因。 - 4: 挂起一个队列不会让一个已经执行的任务停止.
- 5: NSOperationQueue并不能将单个的NSOperation进行挂起操作,NSOperation自身也无法将自己暂停后再进行恢复操作,当NSOperation取消了之后,你再也无法对其进行恢复操作了,在NSOperationQueue上,是无法实现的。
这也是在AFNetworking2.5版本和之前的版本中,如果你想取消一个网络请求,需要这样做的原因。operation.isCancelled.
39EDBC47-D78E-45F8-B458-2FAEEC238A23.png直接通过调用 start 方法来执行一个 operation ,但是这种方式并不能保证 operation 是异步执行的。NSOperation 类的 isConcurrent 方法的返回值标识了一个 operation 相对于调用它的 start 方法的线程来说是否是异步执行的。在默认情况下,isConcurrent 方法的返回值是 NO ,也就是说会阻塞调用它的 start 方法的线程。那么如何创建一个异步的操作呢?
这就需要我们自定义一个并发执行的** operation**
自定义concurrentOperation
BC583DD8-F5E0-447F-A289-875631ECD2EF.png自定义的concurrentOperation 稍微麻烦一点。
在YYWebImageOperation类中,为了更好的管理图片的下载请求和缓存和并发请求,使用了并发的Operation。
并发operation.png 1F94F92C-98B5-4E31-A932-8933D8F60983.png#import <Foundation/Foundation.h>
typedef void(^completeHanderBlock)(NSURLResponse *response, NSData *data, NSError *connectionError);
@interface ImageDownloadOperation : NSOperation
/**
* 图片地址
*/
@property (nonatomic, copy) NSString *imageUrl;
@property (nonatomic, copy) completeHanderBlock completeBlock;
/**
* 下载图片的网路请求类
*
* @param url 下载的网址
* @param downloadBlock 回调
* @return 实例
*/
+ (instancetype)operationWithImageUrl:(NSString *)url
completeHander:(completeHanderBlock)downloadBlock;
@end
#import "ImageDownloadOperation.h"
#import "NSString+FileString.h"
#define WEAKSELF typeof(self) __weak weakSelf = self;
#define STRONGSELF typeof(self) __strong strongSelf = self;
#define STRONGTOWEAK typeof(self) __strong weakSelfToStrong = weakSelf;
@interface ImageDownloadOperation (){
BOOL executing;
BOOL finished;
}
@property (nonatomic, copy) NSString *md5String;
@property (nonatomic, copy) NSString *filePathString;
- (void)completeOperation;
@end
@implementation ImageDownloadOperation
+ (instancetype)operationWithImageUrl:(NSString *)url
completeHander:(completeHanderBlock)downloadBlock{
ImageDownloadOperation *operation = [[ImageDownloadOperation alloc] init];
operation.imageUrl = url;
operation.completeBlock = downloadBlock;
return operation;
}
- (id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)main {
if (_imageUrl.length <= 0) {
[self completeOperation];
return;
}
// 生成文件路径
self.md5String = [NSString MD5HashWithString:_imageUrl];
self.filePathString = [NSString pathWithFileName:self.md5String];
// 文件如果存在则直接读取
BOOL exist = [[NSFileManager defaultManager] fileExistsAtPath:self.filePathString isDirectory:nil];
if (exist) {
NSData *data = [NSData dataWithContentsOfFile:self.filePathString];
self.completeBlock(nil,data,nil);
[self completeOperation];
return;
}
NSURL *url = [NSURL URLWithString:self.imageUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];
NSURLSession *sharedSession = [NSURLSession sharedSession];
WEAKSELF
NSURLSessionDataTask *dataTask = [sharedSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
STRONGTOWEAK
NSLog(@"%@",[NSThread currentThread]);
if (data && (error == nil)) {
NSLog(@"data=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[weakSelfToStrong writeData:data toPath:weakSelfToStrong.filePathString];
} else {
NSLog(@"error=%@",error);
}
weakSelfToStrong.completeBlock(response,data,error);
[weakSelfToStrong completeOperation];
}];
[dataTask resume];
// 让线程不结束
do {
@autoreleasepool {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
if (self.isCancelled) {
[self completeOperation];
}
}
} while (self.isExecuting && self.isFinished == NO);
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled]){
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)writeData:(NSData *)data toPath:(NSString *)path {
//文件操作,需要注意两点:1: 不能同时读写。2:需要判断路径是否是唯一
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[data writeToFile:path atomically:YES];
});
}
@end
#pragma mark -图片下载方法
- (void)downloadImage{
NSString *imageUrlStrin1 = @"http://ww2.sinaimg.cn/mw690/643be833gw1fba9vmlh08j21o42hc4qq.jpg";
NSString *imageUrlString2 = @"http://wx4.sinaimg.cn/mw690/68147f68ly1fbnkw2voj1j207w04y3ye.jpg";
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
ImageDownloadOperation *imageDownOperation1 = [ImageDownloadOperation operationWithImageUrl:imageUrlStrin1 completeHander:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (data.length<=0) {
return ;
}else{
self.imageView1.image = [UIImage imageWithData:data];
}
}];
ImageDownloadOperation *imageDownOperation2 = [ImageDownloadOperation operationWithImageUrl:imageUrlString2 completeHander:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (data.length<=0) {
return ;
}else{
self.imageView2.image = [UIImage imageWithData:data];
}
}];
[queue addOperation:imageDownOperation1];
[queue addOperation:imageDownOperation2];
}