本文主要介绍NSOperation相关知识,在此之前要先说说iOS中另外一个多线程实现方式的CGD。
GCD
gcd的实现细节这里不讲,推荐大家一篇文章,写的很详细。
GCD 系列知识总结
这里主要总结一下使用GCD时,程序说做的事情。两张图片给你整的明明白白。
关系图片01.png 关系图片02.png两张图片主要说明的是队列、任务与线程之间的关系。
对于线程搞不明白的可以看看这个文章,从第一视角的角度,描述了线程的工作。
我是一个线程
NSOperation
对于NSOperation实现多线程的方式有两种:
- 将要执行的任务封装到一个 NSOperation 对象中。
- 将此任务添加到一个 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的多图片下载
老铁,学习就是为啦用啊,不过这个例子只用啦那么一丢丢。讲究看啦。
先上一张图:
此图就是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的使用,会有很大收获的。
网友评论