NSOperation的那些事

作者: AKyS佐毅 | 来源:发表于2017-01-12 18:10 被阅读1233次

    自定义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];
    
    }
    
    

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

    相关文章

      网友评论

        本文标题:NSOperation的那些事

        本文链接:https://www.haomeiwen.com/subject/dbndbttx.html