iOS开发iOS开发技术收集 -- 完整应用及开发技巧篇iOS开发技术收集 -- 理论知识及学习资料篇

NSOperation相关知识总结

2018-04-13  本文已影响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的使用,会有很大收获的。

上一篇 下一篇

猜你喜欢

热点阅读