2018/4/14 21:01:37当前位置推荐好文程序员浏览文章

本文主要详情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 有一个参数 maxConcurrentOperation最大并发数,消耗来设置最多能让多少个任务同时执行。当你把它设置为 1 的时候,他不就是串行了嘛!(注意!!!!尽管他们是串行着来的,可是他们可可以并不在一个线程里面哦)    queue.maxConcurrentOperation= 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还是会异步执行不受 maxConcurrentOperation的影响。    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 related APIs not availablelet 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的使消耗,会有很大收获的。

网友评论