iOS 多线程之 NSOperation

前序:按顺序阅读更好编程

概念篇-进程与线程,任务和队列缓存

iOS 多线程之GCDmarkdown

iOS 多线程之 NSOperation网络

一 简介

NSOperation是对GCD的包装,GCD只支持FIFO的队列,而NSOpration能够设置最大并发数、设置优先级、添加依赖关系等调整执行顺序,NSOpration甚至能够跨队列设置依赖关系多线程

NSOperatio有2个核心概念:NSOperation(操做)和NSOperationQueue(队列). NSOperation是个抽象类,依赖于子类NSInvocationOperation、NSBlockOperation去实现,另外还能够自定义NSOperation.并发

二 基本使用

1. NSInvocationOperation

① 基本使用异步

- (void)invocationOperation {
   // 处理事务
   NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
   selector:@selector(handleInvocation:) object:@"a"];
   // 建立队列
   NSOperationQueue *queue = [[NSOperationQueue alloc] init];
   // 操做加入队列
   [queue addOperation:op];
}
复制代码

② 直接处理事务,不添加隐性队列函数式编程

- (void)invocationOperation {
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"a"];
    [op start];
}
复制代码

③ 错误使用函数

- (void)invocationOperation {
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"a"];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op];
    [op start];
}
--------------------错误日志:-------------------
something is trying to start the receiver simultaneously from more than one thread'
--------------------错误日志:-------------------
复制代码

上述代码之因此会崩溃,是由于线程生命周期:post

[queue addOperation:op]已经将处理事务的操做任务加入到队列中,并让线程运行 [op start]将已经运行的线程再次运行会形成线程混乱

  1. NSBlockOperation

NSInvocationOperation和NSBlockOperation二者的区别在于:

前者相似target形式; 后者相似block形式——函数式编程,业务逻辑代码可读性更高

- (void)blockOperation {
    // 初始化添加事务
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1————%@",[NSThread currentThread]);
    }];
    // 添加事务1
    [bo addExecutionBlock:^{
        NSLog(@"任务2————%@",[NSThread currentThread]);
    }];
    // 添加事务2
    [bo addExecutionBlock:^{
        NSLog(@"任务3————%@",[NSThread currentThread]);
    }];
    // 添加事务3
    [bo addExecutionBlock:^{
        NSLog(@"任务4————%@",[NSThread currentThread]);
    }];
    // 添加事务4
    [bo addExecutionBlock:^{
        NSLog(@"任务5————%@",[NSThread currentThread]);
    }];
    // 回调监听
    bo.completionBlock = ^{
        NSLog(@"completionBlock执行,任务所有完成");
    };
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo];
    NSLog(@"事务添加进了NSOperationQueue");
}
--------------------输出结果:-------------------
事务添加进了NSOperationQueue
任务1————<NSThread: 0x280be7d00>{number = 3, name = (null)}
任务4————<NSThread: 0x280be7d00>{number = 3, name = (null)}
任务3————<NSThread: 0x280bc0340>{number = 6, name = (null)}
任务2————<NSThread: 0x280bd8600>{number = 5, name = (null)}
任务5————<NSThread: 0x280be7d00>{number = 3, name = (null)}
completionBlock执行,任务所有完成
--------------------输出结果:-------------------
复制代码

NSOperationQueue是异步执行的,因此任务12345的完成顺序不肯定,可是completionBlock回调会在任务所有完成后执行

还能够这样用

- (void)blockOperation {
    // 初始化添加事务
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1————%@",[NSThread currentThread]);
    }];
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2————%@",[NSThread currentThread]);
    }];
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务3————%@",[NSThread currentThread]);
    }];
    NSBlockOperation *bo4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务4————%@",[NSThread currentThread]);
    }];
    // bo1的回调监听
    bo1.completionBlock = ^{
        NSLog(@"bo1 的completionBlock执行,任务完成");
    };
    // bo2的回调监听
    bo2.completionBlock = ^{
        NSLog(@"bo2 的completionBlock执行,任务完成");
    };
    // bo3的回调监听
    bo3.completionBlock = ^{
        NSLog(@"bo3 的completionBlock执行,任务完成");
    };
    // bo4的回调监听
    bo4.completionBlock = ^{
        NSLog(@"bo4 的completionBlock执行,任务完成");
    };
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo1];
    [queue addOperation:bo2];
    [queue addOperation:bo3];
    [queue addOperation:bo4];
    NSLog(@"事务添加进了NSOperationQueue");
}
--------------------输出结果:-------------------
事务添加进了NSOperationQueue
任务1————<NSThread: 0x2834dd200>{number = 7, name = (null)}
任务4————<NSThread: 0x2834d62c0>{number = 4, name = (null)}
任务2————<NSThread: 0x2834dd7c0>{number = 6, name = (null)}
任务3————<NSThread: 0x2834d6fc0>{number = 5, name = (null)}
bo4 的completionBlock执行,任务完成
bo1 的completionBlock执行,任务完成
bo3 的completionBlock执行,任务完成
bo2 的completionBlock执行,任务完成
--------------------输出结果:-------------------
复制代码
3 设置优先级
- (void)blockOperation {
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            //sleep(1);
            NSLog(@"第一个操做 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置最高优先级
    bo1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"第二个操做 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置最低优先级
    bo2.qualityOfService = NSQualityOfServiceBackground;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:bo1];
    [queue addOperation:bo2];
}
复制代码

NSOperation设置优先级只会让CPU有更高的概率调用,不是说设置高就必定所有先完成

  • 不使用sleep——高优先级的任务一先于低优先级的任务二
  • 使用sleep进行延时——高优先级的任务一慢于低优先级的任务二
4 线程间通信

在GCD中使用异步进行网络请求,而后回到主线程刷新UI.NSOperation中也有相似在线程间通信的操做

- (void)operationQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Felix";
    [queue addOperationWithBlock:^{
        NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
        }];
    }];
}
复制代码
5 设置并发数

在GCD中只能使用信号量来设置并发数,而NSOperation能够直接经过设置maxConcurrentOperationCount来设置并发数

- (void)operationQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"a";
    queue.maxConcurrentOperationCount = 2;
    
    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{ // 一个任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}
复制代码
6 添加依赖

在NSOperation中经过addDependency添加依赖能很好的控制任务执行的前后顺序

- (void)operationQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"请求token");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,请求数据1");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着数据1,请求数据2");
    }];
    
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    
    [self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
    
    NSLog(@"所有执行完毕");
}

--------------------输出结果:-------------------
请求token
拿着token,请求数据1
拿着数据1,请求数据2
所有执行完毕
--------------------输出结果:-------------------
复制代码
7. 任务的挂起、继续、取消
// 挂起
queue.suspended = YES;
// 继续
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
复制代码
  • 暂停操做不能使当前正在处于执行状态的任务暂停,而是该任务执行结束,后面的任务不会执行,处于排队等待状态。而是该任务执行结束,后面的任务不会执行,处于排队等待状态。
  • 取消操做跟暂停类似,当前正在执行的任务不会当即取消,而是后面的全部任务永远再也不执行,且该操做是不能够恢复的

三 自定义NSOperation缓存机制

根据SDWebImage加载网络图片的缓存机制,用NSOperation自定义图片缓存(本地缓存+内存缓存)

  • 若是内存中有数据,则从内存中取出图片来展现
  • 若是沙盒中有数据,则从沙盒中取出图片来展现并存一份到内存中
  • 若是都没有就异步下载把图片数据写到本地缓存和内存缓存中
-(void)simulationCacheImage{
    UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
    if (cacheImage) {
        NSLog(@"从内存获取图片:%@", model.title);
        cell.imageView.image = cacheImage;
        return cell;
    }

    UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
    if (diskImage) {
        NSLog(@"从沙盒获取image:%@",model.title);
        cell.imageView.image = diskImage;
        [self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
        return cell;
    }

    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"去下载图片:%@", model.title);
        // 延迟
        NSData *data   = [NSData dataWithContentsOfURL:imageURL];
        UIImage *image = [UIImage imageWithData:data];
        // 存内存
        [self.imageCacheDict setValue:image forKey:model.imageUrl];
        [data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];

        // 更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            cell.imageView.image = image;
        }];
    }];

    [self.queue addOperation:bo];
}
复制代码
相关文章
相关标签/搜索