iOS多线程:NSOperation/NSOperationQueue
NSOperation
NSOperation
是基于GCD实现的。相对GCD来说,可控性更强,并且可以添加操作依赖。
NSOperation
是一个抽象基类,可以为子类提供线程安全的建立状态、优先级、依赖和取消等操作。
默认情况下,NSOperation
单独使用时系统在当前线程同步执行操作。
使用NSOperation的方式
使用系统子类:
NSBlockOperation
、NSInvocationOperation
(因为ARC下不是类型安全的,swift中废弃该类)1
2
3
4
5
6
7
8
9
10
11// NSInvocationOperation
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"zh"];
/* 调用start会马上执行封装好的操作 */
[operation1 start];
// NSBlockOperation
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行了一个操作");
}];
/*开始执行任务*/
[operation2 start];自定义
NSOperation
一般通过重写
main
或者start
方法来定义自己的NSOperation
对象。重写
main
方法比较简单,我们不需要管理操作的状态属性isExecuting
和isFinished
。当main()
执行完返回的时候,这个操作就结束了。1
2
3
4
5
6
7class ImageFilterOperation: Operation {
var inputImage:UIImage?
var outputImage:UIImage?
override func main(){
outputImage = filter(image:inputImage)
}
}
NSOperationQueue
特点
NSOperationQueue
是可以运行多个Operation
的队列。- 它并不局限于先进先出的队列操作。
- 提供了多个接口可以实现暂停、继续、中止、优先顺序、依赖等复杂操作。
- 可以通过设置
maxConcurrentOperationCount
来区分是串行还是并行。 - 任务一旦加入队列就开始执行
NSOperationQueue的应用
由于默认情况下,
NSOperation
单独使用时系统在当前线程同步执行操作,所以配合NSOperationQueue
才能实现异步执行。将创建的
NSOperation
添加到NSOperationQueue
,系统会自动将NSOperationQueue
中的NSOperation
取出来,在新线程中执行操作。**在
NSOperationQueue
中使用NSBlockOperation
**:- 一个
NSBlockOperation
对象可以封装多个操作 NSBlockOperation
是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程,开启的线程数是由系统来决定的。- 凡是添加到主队列的操作,都会放到主线程执行。但是使用
addExecutionBlock:
添加的额外操作可能会在其他线程执行:
1
2
3
4
5
6
7
8
9
10
11
12
13let op1 = BlockOperation.init {
print("Block:\\(Thread.current)")
}
op1.addExecutionBlock {
print("executionBlock:\\(Thread.current)")
}
OperationQueue.main.addOperation {
op1.start()
}
//打印结果:
Block:<NSThread: 0x600001ede140>{number = 1, name = main}//主线程执行init里的操作
executionBlock:<NSThread: 0x600001edfec0>{number = 4, name = (null)}//子线程执行executionBlock操作- 一个
NSOperationQueue的**cancel()**方法
在
NSOperationQueue
中有个cancel()
方法。它做的唯一工作就是将Operation的isCancelled
属性从false
改为true
。由于它并不会真正深入代码将某个具体执行的工作暂停,所以必须要利用
isCancelled
属性的变化来暂停main()
方法中的工作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class ArraySumOperation:Operation {
let nums:[Int]
var sum: Int
init(nums:[Int]){
self.nums = nums
self.sum = 0
super.init()
}
override func main(){
for num in nums {
// 根据isCancelled属性来判断是否要暂停操作
if isCancelled {
return
}
sum += num
}
}
}
let queue = OperationQueue()
let someOperation = ArraySumOperation(nums:Array(1...1000))
// 一旦加入OperationQueue中,Operation就开始执行
queue.addOperation(someOperation)
someOperation.cancel()
// someOperation在彻底完成前已经暂停,sum值小于500500
someOperation.completionBlock = { print(someOperation.sum)}
自定义队列
**添加到自定义队列的操作会自动放到子线程中执行**,同时包含了串行、并发功能。
添加操作到队列有两种方法:
addOperation:
和addOperationWithBlock:
1
2
3
4
5
6NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加操作到队列中
[queue addOperation:someOperation];
[queue addOperationWithBlock:^{
//doSomething;
}];
最大并发操作数——
maxConcurrentOperationCount
- 用来控制一个队列中同时并发执行的操作数,而不是并发线程的数量。一个操作也并非只能在一个线程中执行。
- 默认值为
-1
:表示不限制,可以进行并发执行 - 等于1:表示队列为串行队列,只能串行执行
- 大于1:表示队列为并发队列,操作并发执行。但是这个值不应该超过系统限制。
NSOperation的操作依赖
通过操作依赖可以控制操作之间的执行先后顺序。
1
2
3
4
5
6
7
8//添加依赖
-(void)addDependency:(NSOperation *)op;
//移除依赖,取消当前操作对操作op的依赖
-(void)removeDependency:(NSOperation *)op;
//当前操作开始执行之前完成执行的所有操作对象
@property(readonly, copy) NSArray<NSOperation *> *dependencies;NSOperation的优先级
- NSOperation提供了
queuePriority
属性,该属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。 - 默认情况下新创建的操作对象优先级都是
NSOperationQueuePriorityNormal
。 - 可以通过
setQueuePriority:
方法来改变当前操作在同一队列中的执行优先级。
1
2
3
4
5
6
7
8//优先级枚举值
typedef NS_ENUM(NSInteger,NSOperationQueuePriority){
NSOperationQueuePriorityLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriotityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};- NSOperation提供了
操作的状态和执行顺序
NSOperation的状态有:
ready(准备就绪)/
isExecuting(正在执行)/
isCancelled(已取消)/
isFinished`(已完成)对于添加到队列中的操作,是否进入就绪状态取决于操作之间的依赖关系
当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行。
进入就绪状态的操作的开始执行顺序由操作之间相对的优先级(
queuePriority
属性)决定。
queuePriority
属性决定了进入准备就绪状态下的操作之间的开始执行顺序:- 如果一个队列中既有高优先级操作,又有低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。
- 如果一个队列中既有
ready
状态的操作,又有未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高,那么,虽然准备就绪的操作优先级低,也会优先执行。
队列的暂停和取消
- 队列的暂停和取消,并不是将当前的操作立即暂停或取消,而是在当前操作执行完毕之后不再执行新的操作。
- 暂停之后还可以恢复,继续向下执行;
- 取消之后,所有的操作就清空了,无法继续执行剩下的操作。
操作的取消
- 取消操作对象会将该对象保留在队列中,但会通知该对象它应尽快停止其任务。
- 对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止正在执行的操作,并将自身标记为完成。
- 对于已排队但尚未执行的操作,队列必须仍然调用操作对象的
start()
方法,以便它可以处理取消事件并将其自身标记为finished
。
取消操作会导致该操作忽略其可能具有的任何依赖关系。此行为使队列有可能尽快执行操作的
start()
方法。该start()
方法又将操作移至完成状态,以便可以将其从队列中删除。
在多线程中使用单个的operationQueue
对象是线程安全的。不需要加锁来同步操作。
Swift中,OperationQueue
使用Dispatch框架来启动其操作的执行。无论操作被指定为同步还是异步,操作始终在单独的线程上执行。