GCD是C语言编写的API,对于习惯了面向对象的我们来说可能用起来不习惯,所以Apple又为我们提供了另一种多线程解决方案——使用NSOperation(操作,相当于GCD中的任务)、NSOperationQueue(队列)实现多线程。
1. 使用NSOperation子类封装操作
NSOperation是一个抽象类,并不能封装操作。我们想封装操作有两种方式:
(1)使用系统提供的NSOperation的两个子类NSInvocationOperation和NSBlockOperation。
(2)自定义继承于NSOperation的子类,实现- (void)main
方法。
- (void)viewDidLoad {
[super viewDidLoad];
// 利用NSInvocationOperation封装操作
// 可以利用object传递参数
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(executeTask1:) object:@{@"key":@"value"}];
[invocationOperation start];
// 利用NSBlockOperation封装操作
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行操作2----%@",[NSThread currentThread]);
}];
[blockOperation start];
}
- (void)executeTask1:(id)object
{
NSLog(@"执行操作1----%@",[NSThread currentThread]);
}
日志
操作1参数----{
key = value;
}
执行操作1----{number = 1, name = main}
执行操作2----{number = 1, name = main}
可以看到,NSOperation的子类如果只是封装单个操作,操作都是在当前线程执行的,并没有开启新的线程。
下面看一下自定义NSOperation子类的使用,以下载图片为例。新建一个CustomOperation类,继承于NSOperation。
CustomOperation.h
#import
#import
@interface CustomOperation : NSOperation
// 传入URL
@property (nonatomic, copy) NSString *strURL;
// 下载完成图片的回调
@property (nonatomic, copy) void (^downLoadImageBLock)(UIImage *image);
@end
CustomOperation.m
#import "CustomOperation.h"
@implementation CustomOperation
// 1.实现main方法
- (void)main {
// 2.新建一个自动释放池
@autoreleasepool {
// 3.正确响应取消事件
if (self.isCancelled) return;
NSURL *url = [NSURL URLWithString:self.strURL];
NSData *data = [NSData dataWithContentsOfURL:url];
if (self.isCancelled)
{
url = nil;
data = nil;
}
UIImage *image = [UIImage imageWithData:data];
if (self.isCancelled)
{
image = nil;
return;
}
// 4.在主线程执行回调
dispatch_async(dispatch_get_main_queue(), ^{
//5. 用block传值
if (self.downLoadImageBLock) {
self.downLoadImageBLock(image);
}
});
}
}
下面来看看如何使用自定义的NSOperation子类。
// 点击屏幕下载图片
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1.创建一个CustomOperation对象
CustomOperation *operation = [[CustomOperation alloc] init];
// 2.为URL赋值
operation1.strURL = @"http://h.hiphotos.baidu.com/zhidao/pic/item/6d81800a19d8bc3ed69473cb848ba61ea8d34516.jpg";
// 3.处理block传回来的结果
operation1.DownLoadImageBLock = ^(UIImage *image) {
self.imageView1.image = image;
};
// 开始执行操作
[operation start];
}
2. 使用NSOperationQueue实现多线程并发
利用 NSOperationQueue和NSOperation配合使用实现多线程并发的效果只需要两步:
(1)利用NSOperation的子类封装操作。
(2)把操作放入队列中(NSOperationQueue对象)中。
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 封装操作
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作1开始执行");
// 模拟耗时操作
sleep(3);
NSLog(@"执行操作1------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作2开始执行");
sleep(1);
NSLog(@"执行操作2------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作3开始执行");
sleep(7);
NSLog(@"执行操作3------%@",[NSThread currentThread]);
}];
// 把操作放入队列中
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
}
日志
操作1开始执行
操作2开始执行
操作3开始执行
执行操作2------{number = 3, name = (null)}
执行操作1------{number = 4, name = (null)}
执行操作3------{number = 5, name = (null)}
本次在队列中添加了3个操作,系统新创建了3个线程,3个操作并发执行。需要注意的是并不是在队列中添加几个操作,系统就创建几个线程,这是由CPU的使用情况决定。另外,队列里的操作都不会在当前线程中执行。
3.使用maxConcurrentOperationCount控制并发数量和实现串行
另外我们可以决定使用maxConcurrentOperationCount
并发执行的操作个数。`queue.maxConcurrentOperationCount = 2;就是两个操作同时开始执行。
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发数
queue.maxConcurrentOperationCount = 1;
// 封装操作
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作1开始执行");
// 模拟耗时操作
sleep(3);
NSLog(@"执行操作1------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作2开始执行");
sleep(1);
NSLog(@"执行操作2------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作3开始执行");
sleep(7);
NSLog(@"执行操作3------%@",[NSThread currentThread]);
}];
// 把操作放入队列中
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
}
日志
操作2开始执行
操作1开始执行
执行操作2------{number = 3, name = (null)}
操作3开始执行
执行操作1------{number = 4, name = (null)}
执行操作3------{number = 5, name = (null)}
注意:
(1)设置并发执行的个数必须在操作放入队列中- (void)addOperation:(NSOperation *)op;
前面,否则不起效果。
(2)我们并不是设置并发执行的个数为多少,就会真的有多少个操作并发执行,这只是一个期望值,如果设置的值比系统默认最大并发数大,就会以系统默认最大并发数为准。例如系统默认最大并发数为5,你设置了100,最后这个maxConcurrentOperationCount
仍然是5。
(3)这个属性maxConcurrentOperationCount
默认值-1,意思不做限制。
实现串行只要设置queue.maxConcurrentOperationCount = 1
就可以了,操作就会按着FIFO的原则顺序执行。
日志
操作1开始执行
执行操作1------{number = 3, name = (null)}
操作2开始执行
执行操作2------{number = 3, name = (null)}
操作3开始执行
执行操作3------{number = 3, name = (null)}
这里需要注意的是虽然是串行,但是并不表示他们会在同一个线程中执行,这次只是例外, 可以试试在队列里添加10000个操作。
4.控制操作的执行顺序
如果我们想一个操作执行完了,另一个操作才能继续执行,可以添加依赖。
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 封装操作
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作1开始执行");
// 模拟耗时操作
sleep(3);
NSLog(@"执行操作1------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作2开始执行");
sleep(1);
NSLog(@"执行操作2------%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作3开始执行");
sleep(7);
NSLog(@"执行操作3------%@",[NSThread currentThread]);
}];
// 为operation2添加依赖operation3, 表示operation3执行完了,operation2才能执行
[operation2 addDependency:operation3];
[operation1 addDependency:operation2];
// 把操作放入队列中
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
}
日志
操作3开始执行
执行操作3------{number = 3, name = (null)}
操作2开始执行
执行操作2------{number = 3, name = (null)}
操作1开始执行
执行操作1------{number = 3, name = (null)}
5.NSOperation的其他用法
5.1 利用NSBlockOperation添加多个操作
可以利用NSBlockOperation的- (void)addExecutionBlock:(void (^)(void))block;
方法添加多个操作。
- (void)viewDidLoad {
[super viewDidLoad];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
sleep(10);
NSLog(@"执行任务1----%@", [NSThread currentThread]);
}];
// 添加新操作
[operation addExecutionBlock:^{
sleep(8);
NSLog(@"执行任务2----%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
sleep(7);
NSLog(@"执行任务3----%@",[NSThread currentThread]);
}];
[operation start];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"用户点击了");
}
日志
执行任务2----{number = 3, name = (null)}
执行任务1----{number = 1, name = main}
执行任务3----{number = 3, name = (null)}
用户点击了
这里需要注意的是NSOperation会把部分操作放到当前线程里执行。如果当前线程是主线程并且是耗时操作,会堵塞主线程,影响用户交互。
5.2 执行完的回调
NSOperation还可以实现CGD group-notify的功能。
// 当operation里所有的操作执行完了执行block里面的代码
operation.completionBlock = ^{
NSLog(@"所有的操作都执行完了");
};
5.3 设置优先级
可以使用@property NSOperationQueuePriority queuePriority;设置NSOperation的优先级,默认是
NSOperationQueuePriorityNormal`。优先级一共有五个值:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,// 优先级很低
NSOperationQueuePriorityLow = -4L,// 优先级低
NSOperationQueuePriorityNormal = 0,// 默认
NSOperationQueuePriorityHigh = 4,// 优先级高
NSOperationQueuePriorityVeryHigh = 8// 优先级很高
};
5.4 操作的取消、执行、完成
NSOperation的三个属性cancelled、executing、finished,分别就是取消,执行,完成。这三个属性都是只读的,我们通过这三个属性可以判断NSOperation的状态,是否取消了,是否正在执行,是否已经完成了。
6.NSOperationQueue的其他用法
1)暂停:
[queue setSuspended:YES];
2)恢复:
[queue setSuspended:NO];
3)判读队列的当前状态:
@property (getter=isSuspended) BOOL suspended;
当你把队列暂停时,队列里的操作就不执行了。当你滑动列表时,可以先把队列(队列里执行下载图片的操作,列表里的cell上有图片)暂停,不滑动时再恢复,增加APP的流畅性。
4)取消所有操作
- (void)cancelAllOperations;
队列里的所有操作都不执行了。
以上就是关于NSOperation的常用操作,用这些基本上就能够满足我们的需求。如果还满足不了怎么办,自定义NSOperation,下一篇文章讲自定义NSOperation。