@qidiandasheng
2016-09-18T19:13:29.000000Z
字数 7273
阅读 2366
iOS理论
NSOperation
是在OC中处理多线程的一种方式,我们可以配合使用NSOperation
和NSOperationQueue
实现多线程编程。实现过程大致如下:
1:先将需要执行的操作封装到一个NSOperation对象中
2:然后将NSOperation对象添加到NSOperationQueue中
3:系统会自动将NSOperation中封装的操作放到一条新线程中执行
在此过程中,我们根本不用考虑线程的生命周期、同步、加锁等问题。
默认情况下,NSOperation
并不具备封装操作的能力,必须使用它的子类,使用NSOperation
子类的方式有3种:
1:NSInvocationOperation
2:NSBlockOperation
3:自定义子类继承NSOperation,实现内部相应的方法
这是第一种封装操作的方式。我们现在来写一个最简单的例子:
- (void)operationStart{
//初始化了一个NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeName:) object:@"齐滇大圣"];
//开始执行operation里面封装好的操作
[operation start];
NSLog(@"operation end");
}
- (void)writeName:(NSString *)name{
NSLog(@"%@ %@",name,[NSThread currentThread]);
sleep(3);
}
//输出
2016-09-04 10:14:50.884 NSOperationDemo[58483:8251571] 齐滇大圣 <NSThread: 0x7fb2a0507e20>{number = 1, name = main}
2016-09-04 10:14:53.887 NSOperationDemo[58483:8251571] operation end
我们看到[operation start];
后writeName
方法开始执行,而这里输出的线程是在主线程。我们看一下输出的时间发现要等writeName
操作结束后(3s后)NSLog(@"operation end");
才执行。
所以这种直接[operation start];
是同步的在当前线程上执行,会阻塞当前线程。一般不太推荐使用。
那如果我们不想同步怎么办,那可以把operation
加入到NSOperationQueue
中。
- (void)operationStart{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeName:) object:@"齐滇大圣"];
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:operation];
NSLog(@"operation end");
}
- (void)writeName:(NSString *)name{
NSLog(@"%@ %@",name,[NSThread currentThread]);
sleep(3);
}
//输出:
2016-09-04 10:21:58.067 NSOperationDemo[58599:8258279] operation end
2016-09-04 10:21:58.069 NSOperationDemo[58599:8258279] 齐滇大圣 <NSThread: 0x7fc461601f80>{number = 1, name = main}
我们看到当加入到NSOperationQueue
中的时候,操作还是在主线程执行的,但是是异步的,没有阻塞当前线程。两个输出的时间几乎是同时的。
[NSOperationQueue mainQueue]
我们能看出这个是获取主线程上的操作队列。我们也可以把[NSOperationQueue mainQueue]
换成[NSOperationQueue new]
(自己创建的操作队列)。
我们发现writeName
任务就没在主线程上执行了。相当于你创建的操作队列里面的operation
系统会自动给你分配一个线程去处理里面封装的操作。
这种方式相对于上一种其实就是把任务封装成了一个block
加入到了operation
中。
- (void)operationStart{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
sleep(3);
}];
// 开始执行任务
[operation start];
NSLog(@"operation end");
}
//输出:
2016-09-04 10:47:26.129 NSOperationDemo[58899:8277981] 齐滇大圣 <NSThread: 0x7f8969604da0>{number = 1, name = main}
2016-09-04 10:47:29.132 NSOperationDemo[58899:8277981] operation end
我们看一下两个输出的时间差,发现这种方式其实也是同步的,会阻塞当前线程。
那是不是如上一种方式一样把operation
放入NSOperationQueue
中就行了呢?我们来看一下:
//加入到mainQueue中
- (void)operationStart{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
sleep(3);
}];
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:operation];
NSLog(@"operation end");
}
//输出:
2016-09-04 10:53:18.406 NSOperationDemo[59091:8284999] operation end
2016-09-04 10:53:18.408 NSOperationDemo[59091:8284999] 齐滇大圣 <NSThread: 0x7ff3b9405ce0>{number = 1, name = main}
//加入到自己创建的`NSOperationQueue`中
- (void)creatOperationStart{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
sleep(3);
}];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation];
NSLog(@"operation end");
}
//输出:
2016-09-04 10:53:21.911 NSOperationDemo[59091:8284999] operation end
2016-09-04 10:53:21.915 NSOperationDemo[59091:8285185] 齐滇大圣 <NSThread: 0x7ff3b942bf90>{number = 2, name = (null)}
我们发现也是可以像NSInvocationOperation
放入mainqueue
或自己创建的NSOperationQueue
中的。都是异步执行。
其实[operation start];
也可以多线程执行的。当operation
中加入了多个block
操作的时候,其实系统就会自动分配新线程实现多并发。
但是相对于整个主线程来说这个operation
还是同步的,也就是说主线程会等所有的operation
里的操作都完成后[operation start];
后面的才继续执行。
下面我们看例子:
- (void)operationStart{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"齐滇大圣 %@",[NSThread currentThread]);
sleep(3);
}];
[operation addExecutionBlock:^{
NSLog(@"A %@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"B %@",[NSThread currentThread]);
sleep(5);
}];
[operation addExecutionBlock:^{
NSLog(@"C %@",[NSThread currentThread]);
}];
// 开始执行任务
[operation start];
NSLog(@"operation end");
}
//输出:
2016-09-04 11:12:58.061 NSOperationDemo[59382:8299618] B <NSThread: 0x7fb5cb4699d0>{number = 2, name = (null)}
2016-09-04 11:12:58.061 NSOperationDemo[59382:8299611] 齐滇大圣 <NSThread: 0x7fb5cb52c1e0>{number = 3, name = (null)}
2016-09-04 11:12:58.061 NSOperationDemo[59382:8299486] A <NSThread: 0x7fb5cb506170>{number = 1, name = main}
2016-09-04 11:12:58.061 NSOperationDemo[59382:8299604] C <NSThread: 0x7fb5cb75d8c0>{number = 4, name = (null)}
2016-09-04 11:13:03.125 NSOperationDemo[59382:8299486] operation end
我们看到operation end
的输出跟之前的相差了5s,我们在B那里sleep
了5s,虽然B不是在主线程上执行的,但是NSLog(@"operation end");
也要等B执行完了之后才能执行。
所以说虽然operation
加入了多个block
任务发生了并发。我们可以理解为这些任务相对于operation
来说它就是一整个完整的任务,下面有多个子任务而已。只有当这一整个任务完成了[operation start];
后面的才能执行。
有时候NSInvocationOperation
和NSBlockOperation
不能满足需求,我们可以直接新建子类继承NSOperation
,并添加任何需要执行的操作。
我们所熟悉的SDWebImage
中的下载操作类SDWebImageDownloaderOperation
就是继承自NSOperation
的子类。
SDWebImageDownloaderOperation这篇文章有讲解SDWebImageDownloaderOperation
如何使用及实现。
自定义的NSOperation
有两种一种是非并发的,一种是并发的。
自定义并发的比较容易,只需要实现以下两个方法:
- 自定义初始化方法
- main方法
自定义非并发的就比较麻烦了,SDWebImageDownloaderOperation
就是自定义非并发的,我们看一下下面这张表:
方法 | 描述 |
---|---|
start | (必选)所有的并发Operation必需重写这个方法并且要实现这个方法的内容来代替原来的操和。手动执行一个操作,你可以调用start方法。因此,这个方法的实现是这个操作的始点,也是其他线程或者运行这你这个任务的起点。注意一下,在这里永远不要调用[super start]。 |
main | (可选)这个方法就是你的实现的操作 |
isExecuting 和 isFinish | (必选)并发队列负责维持当前操作的环境和告诉外部调用者当前的运行状态。因此,一个并发队列必需维持保持一些状态信息以至于知道什么时候执行任务,什么时候完成任务。它必须通过这些方法告诉外部当前的状态。这种而且这些方法必须是线程安全,当状态发生改变的时候,你必须使用KVO通知监听这些状态的对象。 |
isConcurrent | (必选)定义一个并发操作,重写这个方法并且返回YES |
operation
开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作。
当向一个Operation
对象发送cancel
消息后,并不保证这个Operation
对象一定能立刻取消,这取决于你的main
中对cancel
的处理。如果你在main
方法中没有对cancel
进行任何处理的话,发送cancel
消息是没有任何效果的。为了让Operation
响应cancel
消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled
属性,如果返回YES
的话,应释放相关资源并立刻停止继续执行。
上诉的main
可以是start
。
比如SDWebImageDownloaderOperation
类中的- (void)start
:
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
//下面是一些实际的操作
}
}
如果想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情
operation.completionBlock = ^() {
NSLog(@"执行完毕");
};
当operation封装的操作执行完毕后,就会回调Block里面的内容
表示某个operationB
依赖于operationA
,只有当operationA
操作完成的时候,operationB
才开始执行。
[operationB addDependency:operationA];
所以我们用这个就可以实现多个异步操作完成后得到完成的通知,如下:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A");
}];
NSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B");
}];
NSBlockOperation *p3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C");
}];
NSBlockOperation *pfinish = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Finish");
}];
[pfinish addDependency:p1];
[pfinish addDependency:p2];
[pfinish addDependency:p3];
//!!!所以pfinish一定是最后输出,p1、p2、p3异步顺序不一定。
// waitUntilFinished是否阻塞当前线程
[queue addOperations:@[p1,p2,p3,pfinish] waitUntilFinished:YES];
// 如果是NO,那么这行打印就是随机的。YES表示阻塞当前线程那么就是要等A,B,C,Finish都打印完之后才打印HAHA
NSLog(@"HAHA");
- (void)cancelAllOperations;
// YES代表暂停队列,NO代表恢复队列
- (void)setSuspended:(BOOL)b;
//当前状态
- (BOOL)isSuspended;
添加操作有对应的三个方法:
- (void)addOperation:(NSOperation *)op;
//waitUntilFinished表示是否阻塞当前线程,YES表示阻塞
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- (void)addOperationWithBlock:(void (^)(void))block ;
对于NSOperation使用的Demo:NSOperationDemo