NSOperation的那些事

自定义operation

NSOperation的那些事_第1张图片
custom_operation.png

根据文档,每一个operation都必须实现一下方法:

  • 继承NSOperation类,重写初始化方法。
  • 重写main方法。

具体步骤如下:

#import 

@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.

NSOperation的那些事_第2张图片
39EDBC47-D78E-45F8-B458-2FAEEC238A23.png

直接通过调用 start 方法来执行一个 operation ,但是这种方式并不能保证 operation 是异步执行的。NSOperation 类的 isConcurrent 方法的返回值标识了一个 operation 相对于调用它的 start 方法的线程来说是否是异步执行的。在默认情况下,isConcurrent 方法的返回值是 NO ,也就是说会阻塞调用它的 start 方法的线程。那么如何创建一个异步的操作呢?

这就需要我们自定义一个并发执行的** operation**

自定义concurrentOperation

NSOperation的那些事_第3张图片
BC583DD8-F5E0-447F-A289-875631ECD2EF.png

自定义的concurrentOperation 稍微麻烦一点。

在YYWebImageOperation类中,为了更好的管理图片的下载请求和缓存和并发请求,使用了并发的Operation。

NSOperation的那些事_第4张图片
并发operation.png
NSOperation的那些事_第5张图片
1F94F92C-98B5-4E31-A932-8933D8F60983.png
#import 

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];

}

Demo 地址:https://github.com/summerHearts/iOSTips

你可能感兴趣的:(NSOperation的那些事)