NSOperation相关知识总结

作者: 忆辰念家 | 来源:发表于2018-04-13 10:49 被阅读266次

    本文主要介绍NSOperation相关知识,在此之前要先说说iOS中另外一个多线程实现方式的CGD。

    GCD

    gcd的实现细节这里不讲,推荐大家一篇文章,写的很详细。
    GCD 系列知识总结

    这里主要总结一下使用GCD时,程序说做的事情。两张图片给你整的明明白白。

    关系图片01.png 关系图片02.png

    两张图片主要说明的是队列、任务与线程之间的关系。

    对于线程搞不明白的可以看看这个文章,从第一视角的角度,描述了线程的工作。
    我是一个线程

    NSOperation

    对于NSOperation实现多线程的方式有两种:

    1. 将要执行的任务封装到一个 NSOperation 对象中。
    2. 将此任务添加到一个 NSOperationQueue 对象中。

    需要说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

    将要执行的任务封装到一个 NSOperation 对象中。

    不多哔哔直接上代码:

    1.NSInvocationOperation
    /**
     NSInvocationOperation
     */
    - (void)invocationOperationTest{
        
        //1.创建NSInvocationOperation对象
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
        //2.开始执行
        [operation start];
    }
    
    2.NSBlockOperation
    /**
     NSBlockOperation
     */
    - (void)blockOperationTest{
        //1.创建NSBlockOperation对象
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            [self run];
        }];
        //2.开始任务
        [operation start];
    }
    
    

    这样的任务默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。

    继续走一波源码

    - (void)addExecutionBlockTest{
    //    NOTE:addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错:
        
        //1.创建NSBlockOperation对象
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
        }];
        //添加多个Block
        for (NSInteger i = 0; i < 5; i++) {
            [operation addExecutionBlock:^{
                NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
            }];
        }
        //2.开始任务
        [operation start];
    }
    

    将此任务添加到一个 NSOperationQueue 对象中。

    看过上面的内容就知道,我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。

    1.主队列

    只要涉及多线程的,就不能没有主队列额,因为我们只能在这里刷新UI。
    获取主队列的方法
    在主队列里任务是在主线程同步执行

    /**
     获取主队列
     */
    - (void)getMainQueue{
       //获取主队列
        NSOperationQueue *queue = [NSOperationQueue mainQueue];
        
        //2.添加多个Operation
        for (NSInteger i = 0; i < 10; i++) {
            [queue addOperationWithBlock:^{
                NSLog(@"Operation:%ld%@", i, [NSThread currentThread]);
            }];
        }
    }
    
    2.其他队列

    在GCD里存在三种队列:串行、并发、主队列。但是在NSOperation这边就不一样啦,除啦主队列,就是其他队列。
    在其他队列里任务是在其他线程并发执行

    /**
     NSOperationQueue,其他队列的任务会在其他线程并发执行
     */
    - (void)operationQueueTest{
        
        //1.创建一个其他队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //2.创建NSBlockOperation对象
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
        }];
        
        //3.添加多个Block
        for (NSInteger i = 0; i < 5; i++) {
            [operation addExecutionBlock:^{
                NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
            }];
        }
        
        //4.队列添加任务
        [queue addOperation:operation];
        
        //operation 完成任务的回调
        operation.completionBlock = ^{
            NSLog(@"completionBlock");
        };
    }
    

    这里就有个问题啦,如果我就想在非主线程里任务一个个的执行呢。别急,苹果爸爸是很贴心的。

    /**
     NSOperationQueue,其他队列的任务会在其他线程串行执行
     */
    - (void)operationQueueSerialTest{
        
        //1.创建一个其他队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        //这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,他不就是串行了嘛!(注意!!!!虽然他们是串行着来的,可是他们可能并不在一个线程里面哦)
        queue.maxConcurrentOperationCount = 1;
        
        //2.创建多个Operation
        for (NSInteger i = 0; i < 10; i++) {
            [queue addOperationWithBlock:^{
                NSLog(@"Operation:%ld%@", i, [NSThread currentThread]);
            }];
        }
        
        
        //3.创建NSBlockOperation对象
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
        }];
        
        [queue addOperation:operation];
    
        //4.添加多个Block,为operation添加的ExecutionBlock还是会异步执行不受 maxConcurrentOperationCount 的影响。
        for (NSInteger i = 0; i < 10; i++) {
            [operation addExecutionBlock:^{
                NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
            }];
        }
    }
    
    3.Operation之间的约束

    直接上代码吧,程序员不会表达啊。

    /**
     Operation之间的约束
     */
    - (void)dependencyTest{
        //1.任务一:下载图片
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"下载图片 - %@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:1.0];
        }];
        
        //2.任务二:打水印
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"打水印   - %@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:1.0];
        }];
        
        //3.任务三:上传图片
        NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"上传图片 - %@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:1.0];
        }];
        
        //4.设置依赖
        [operation2 addDependency:operation1];      //任务二依赖任务一
        [operation3 addDependency:operation2];      //任务三依赖任务二
        //5.创建队列并加入任务
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
    }
    

    简单粗暴,让你的任务按着你的想法一个个的去工作。

    4.其他属性和方法

    就这么多,能怎么用自己想吧。可以参考一下SDWebImage对于NSOperation的使用。对大神的代码只能膜拜。

    /*
     NSOperation
     BOOL executing; //判断任务是否正在执行
     BOOL finished; //判断任务是否完成
     void (^completionBlock)(void); //用来设置完成后需要执行的操作
     - (void)cancel; //取消任务
     - (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕
     
     
     NSOperationQueue
     NSUInteger operationCount; //获取队列的任务数
     - (void)cancelAllOperations; //取消队列中所有的任务
     - (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
     [queue setSuspended:YES]; // 暂停queue
     [queue setSuspended:NO]; // 继续queue
     */
    

    号外:

    // 在 Swift 构建的和谐社会里,是容不下 NSInvocationOperation 这种不是类型安全的败类的https://stackoverflow.com/questions/26644477/nsinvocationoperation-is-unavailable-in-xcode-6-1

    //'NSInvocationOperation' is unavailable in Swift: NSInvocation and related APIs not available
    let operation = NSInvocationOperation(target:self, #selector(run), object:self)
    

    其他的用法 Swift 和OC是一致的,不啰嗦啦。

    利用NSOperation实现UITableView的多图片下载

    老铁,学习就是为啦用啊,不过这个例子只用啦那么一丢丢。讲究看啦。
    先上一张图:

    cell下载图片思路.png

    此图就是SD原理的一个简略的流程图。主要思想就是这些,当然大神的想法怎么会这么low呢,不存在的额。

    SDWebImage给UIImageView设置图片的运行图.png

    头疼。。。不扯这个啦,有兴趣研究SDWebImage的,给你们个文章。
    SDWebImage优质源码解读笔记(最新版本和旧版本都有)

    言归正传啦,说说 “cell下载图片思路图” 的代码实现吧。

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *ID = @"news";
        NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
        News *news = self.newsArray[indexPath.row];
        cell.titleLabel.text = news.title;
    
        // 先从内存缓存中取图片
        __block UIImage *image = self.imagesCache[news.url];
        // 如果内存缓存中有则直接显示在cell上
        if (image) {
            cell.imgView.image = image;
        }
        else {
            cell.imgView.image = [UIImage imageNamed:@"placeholder"];
            // 如果内存缓存中没有,再去沙盒里面看看
            NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
            // 文件全路径
            NSString *filePath = [cachePath stringByAppendingPathComponent:news.url.lastPathComponent];
            NSData *diskData = [NSData dataWithContentsOfFile:filePath];
            diskData = nil;
            // 如果沙盒有数据
            if (diskData) {
                UIImage *diskImage =[UIImage imageWithData:diskData];
                cell.imgView.image = diskImage;
                // 缓存到内存
                self.imagesCache[news.url] = diskImage;
            }
            else {
                NSBlockOperation *op = self.operationCache[news.url];
                if (!op) {
                    op = [NSBlockOperation blockOperationWithBlock:^{
                        // 根据url进行下载
                        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:news.url]];
                        // 如果网络中断下载失败等导致data为空
                        if (!data) {
                            // 从操作缓存中移除,使得该图片有机会重新下载
                            [self.operationCache removeObjectForKey:news.url];
                            return;
                        }
                        image = [UIImage imageWithData:data];
                        // 写入缓存
                        self.imagesCache[news.url] = image;
                        // 写入沙盒
                        [data writeToFile:filePath atomically:YES];
                        // 回主线程展示
                        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                        }];
    
                        // 下载完成,移除操作
                        [self.operationCache removeObjectForKey:news.url];
                    }];
                    // 进行缓存
                    self.operationCache[news.url] = op;
                    // 添加操作
                    [self.queue addOperation:op];
                }
            }
        }
        return cell;
    }
    

    惭愧惭愧,我自己的想象力没有这么丰富,也是参考大神的文章才有上面的代码。至于大神写这个代码的心路历程可以参考一下文章。
    利用NSOperation实现UITableView的多图片下载

    至此这边文章要说的东西就结束啦,关于NSOperation的学习,还是建议大家去看看比较牛逼的库,比如SDWebImage、AFNetworking对于NSOperation的使用,会有很大收获的。

    相关文章

      网友评论

        本文标题:NSOperation相关知识总结

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