1. 前言
Operation是一个抽象类,一般不直接使用它,而是使用它的子类或使用系统定义的子类(NSInvocationOperation或BlockOperation)来执行实际任务。尽管是抽象的,但Operation的基本实现确包含了协调任务安全执行的重要逻辑。这种内置逻辑允许开发人员将注意力集中在任务的实际实现上,而不是集中在线程相关的逻辑上。 下面来具体看一下。
**作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:[812157648]
2. 主要属性及方法
主要属性如下表:
属性名称 | 类型 | 描述 |
---|---|---|
var isCancelled: Bool { get } | Bool | 当前operation是否被取消。 |
var isExecuting: Bool { get } | Bool | 当前operation是否正在执行。 |
var isFinished: Bool { get } | Bool | 当前operation是否已经执行结束。 |
var isConcurrent: Bool { get } | Bool | 当前operation是否是异步执行,已被isAsynchronous代替。 |
var isAsynchronous: Bool { get } | Bool | 当前operation是否是异步执行。 |
var isReady: Bool { get } | Bool | 当前operation是否已经就绪。 |
var dependencies: [Operation] { get } | Array | 当前operation所依赖的所有operation。 |
var queuePriority: Operation.QueuePriority | QueuePriority | 当前operation在队列中的优先级。 |
var name: String? | String | 当前operation的名称。 |
var completionBlock: (() -> Void)? | Void | 当main()函数调用完成后执行此block。 |
public enum QueuePriority : Int {
case veryLow = -8
case low = -4
case normal = 0
case high = 4
case veryHigh = 8
}
主要方法如下表:
方法名称 | 返回值类型 | 描述 |
---|---|---|
func start() | Void | 执行并发任务的入口。 |
func main() | Void | 执行非并发任务的入口 |
func cancel() | Void | 停止执行任务。 |
func addDependency(_ op: Operation) | Void | 添加依赖。 |
func removeDependency(_ op: Operation) | Void | 移除依赖。 |
func waitUntilFinished() | Void | 阻止当前线程的执行,直到当前的operation执行完毕。 |
3. Operation Dependencies(依赖)
我们可以通过添加依赖的方式,将很多个Operation进行排列,从而达到依次执行的目的。涉及的方法有: addDependency(:) :添加依赖。 removeDependency(:) :移除依赖。
当给某个Operation添加依赖的Operation后,只有其所依赖的所有Operation都执行完毕,当前的Operation才能开始执行。
不管依赖的Operation是执行成功了还是失败了,或者是取消了,都认为是执行完毕了。
下面看一组测试:
class DownloadOperation: Operation {
override func main() {
if !isReady {
return
}
if let n = name {
print(n + " is downloading")
print(n + " \(Thread.current)") // 打印当前线程
}
sleep(2) // 模拟下载时间
}
}
func operationDemo() {
let operation1 = DownloadOperation()
operation1.name = "operation1"
operation1.completionBlock = {
print("operation1 finished")
}
let operation2 = DownloadOperation()
operation2.name = "operation2"
operation2.completionBlock = {
print("operation2 finished")
}
let operation3 = DownloadOperation()
operation3.name = "operation3"
operation3.completionBlock = {
print("operation3 finished")
}
// operation3.addDependency(operation2)
// operation2.addDependency(operation1)
let operationQueue = OperationQueue()
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
operationQueue.addOperation(operation3)
}
新建一个DownloadOperation类,模拟下载任务,在operationDemo()中,创建了三个DownloadOperation对象,并加入到队列中(由队列接管Operation,并异步执行),如果注释掉上面的依赖代码,执行结果如下:
operation2 is downloading
operation3 is downloading
operation1 is downloading
operation1 <NSThread: 0x600001d55580>{number = 2, name = (null)}
operation2 <NSThread: 0x600001d529c0>{number = 4, name = (null)}
operation3 <NSThread: 0x600001d51e00>{number = 5, name = (null)}
operation1 finished
operation3 finished
operation2 finished
如果将依赖的代码打开,执行后的结果如下:
operation1 is downloading
operation1 <NSThread: 0x600002b524c0>{number = 3, name = (null)}
operation1 finished
operation2 is downloading
operation2 <NSThread: 0x600002b524c0>{number = 3, name = (null)}
operation2 finished
operation3 is downloading
operation3 <NSThread: 0x600002b1af80>{number = 6, name = (null)}
operation3 finished
很显然,添加了依赖后,任务执行的更加有序了。
4. 同步或异步方式
如果手动执行Operation对象,而不是将其添加到队列中,则可以采用同步或异步方式执行。默认情况下,Operation对象是同步的。在同步操作中,Operation对象不会创建单独线程。当调用start()方法时,该Operation将立即在当前线程中执行,当其执行完成后,后续任务才会开始执行。 比如将当面的方法改成:
func operationDemo() {
let operation1 = DownloadOperation()
operation1.name = "operation1"
operation1.completionBlock = {
print("operation1 finished")
}
operation1.start()
let operation2 = DownloadOperation()
operation2.name = "operation2"
operation2.completionBlock = {
print("operation2 finished")
}
operation2.start()
let operation3 = DownloadOperation()
operation3.name = "operation3"
operation3.completionBlock = {
print("operation3 finished")
}
operation3.start()
}
执行结果如下:
operation1 is downloading
operation1 <NSThread: 0x600003e28880>{number = 1, name = main}
operation1 finished
operation2 is downloading
operation2 <NSThread: 0x600003e28880>{number = 1, name = main}
operation3 is downloading
operation2 finished
operation3 <NSThread: 0x600003e28880>{number = 1, name = main}
operation3 finished
从结果中可以看出任务是依次执行的,并且都在主线程中。当然这种写法基本不会用到,毕竟耗时操作放到主线程中会阻塞UI的。
当调用异步操作的start()方法时,该方法可能在相应任务完成之前就return了。异步Operation对象会在单独的线程上执行它的任务,这时并不影响调用线程(比如主线程)。 比如下面的代码示例:
func operationDemo() {
let operation1 = DownloadOperation()
operation1.name = "operation1"
operation1.completionBlock = {
print("operation1 finished")
}
DispatchQueue.global().async {
operation1.start()
}
let operation2 = DownloadOperation()
operation2.name = "operation2"
operation2.completionBlock = {
print("operation2 finished")
}
DispatchQueue.global().async {
operation2.start()
}
let operation3 = DownloadOperation()
operation3.name = "operation3"
operation3.completionBlock = {
print("operation3 finished")
}
DispatchQueue.global().async {
operation3.start()
}
print("I am running on main thread")
}
在主线程中异步调用start()方法,并在最后输出"I am running on main thread"。 执行结果如下:
operation2 is downloading
operation3 is downloading
operation1 is downloading
I am running on main thread
operation2 <NSThread: 0x6000031a9f00>{number = 3, name = (null)}
operation3 <NSThread: 0x6000031abb40>{number = 4, name = (null)}
operation1 <NSThread: 0x6000031ab740>{number = 5, name = (null)}
operation2 finished
operation1 finished
operation3 finished
可以看出三个任务各自开启了一个线程处理事件,而且"I am running on main thread"这句话很早就调用了,并没有等三个任务结束才调用。
一般开发中都会用队列去执行我们的Operation,即将Operation添加到队列中,队列执行Operation任务的时候,会直接开启一个新的线程去执行任务,也就是异步执行任务。上面关于依赖的代码就证实了这一点。
5. 并发与非并发
Operation类提供了基本的逻辑来跟踪操作的执行状态,继承的子类只需要做一些实际业务工作。如何创建子类取决于并发执行还是非并发执行。
5.1 非并发Operation
对于非并发的操作,在子类的Operation中,只需要复写main()就可以了。
在main()方法中,写一些任务执行的代码等,另外在子类中可能还需要写一些初始化方法,以及一些访问读取数据的方法等等。
至于Operation的几种状态,我们是不需要关心的,当main()方法执行完毕,即Operation任务结束。
Operation类中还提供了cancel()的方法,所以在Operation执行的时候需要判断是否已经取消了,因为取消操作可能在开始之前就执行了,也可能在任务执行过程中,所以代码中需要加入isCancelled的判断。
例如下面的代码示例:
class MyNonConcurrentOperation: Operation {
var data: Any?
init(data: Any) {
super.init()
self.data = data
}
override func main() {
var isDone = false
while !isCancelled && !isDone {
// 在此方法中做一些相关业务操作等,并在完成后将isDone设置为true
// ......
isDone = true
}
}
}
5.2 并发Operation Operation对象默认以同步方式执行,也就是说,在调用start方法的线程中执行任务。但是,由于OperationQueue为非并发操作提供线程,所以大多数Operation仍然是异步运行的。但是,如果我们手动使用Operation,不用OperationQueue,并且仍然希望它们异步运行,则必须采取适当的操作来确保能够做到这一点。我们可以通过将Operation对象定义为并发操作来实现这一点。 至于并发的Operation,稍微有些复杂了,因为这里面的状态需要开发人员来管控。 如果创建一个并发的Operation,则至少需要复写以下的方法或属性:
属性或方法 | 描述 |
---|---|
start() | 所有的并发Operation都需要复写此方法,以此作为任务的开始,此方法不需要调用super的start方法。 |
isAsynchronous | 定义当前的Operation是否是并发的,返回true,isAsynchronous代替了isConcurrent。 |
isExecuting | 并发操作需要开发人员手动管理isExecuting,因此代码中应该有一个executing的状态,当任务开始的时候发送KVO通知。 |
isFinished | 并发操作需要开发人员手动管理isFinished,因此代码中应该有一个finished的状态,当任务开始的时候发送KVO通知。 |
示例代码如下:
class MyConcurrentOperation: Operation {
var myExecuting: Bool = false
var myFinished: Bool = false
override var isAsynchronous: Bool {
return true
}
override var isExecuting: Bool {
return myExecuting
}
override var isFinished: Bool {
return myFinished
}
override func start() {
// 先判断当前Operation是否已经取消,如果取消则不再执行任务,并将状态设置为finished为true
if isCancelled {
willChangeValue(forKey: "isFinished")
myFinished = true
didChangeValue(forKey: "isFinished")
return
}
// 如果没有取消,那么继续执行任务
willChangeValue(forKey: "isExecuting")
myExecuting = true
Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
didChangeValue(forKey: "isExecuting")
}
override func main() {
/**
.....
执行任务代码
*/
// 执行完任务后调用
completeOperation()
}
func completeOperation() {
willChangeValue(forKey: "isFinished")
willChangeValue(forKey: "isExecuting")
myExecuting = false
myFinished = true
didChangeValue(forKey: "isFinished")
didChangeValue(forKey: "isExecuting")
}
}
6. 取消Operation
当一个Operation添加到队列(OperationQueue)中后,就由队列接管并安排执行了,如果用户想取消某个任务等,则开发人员需要调用Operation的cancel()方法,或者OperationQueue的cancelAllOperations()方法。
取消一个Operation不会立即停止其正在进行任务,Operation类默认添加了isCancelled的判断,如果是自定义的Operation类,那么则需要在start()方法中显示的判断isCancelled,如果取消了,那么直接return,不再执行任务了。
7. 结束语
以上则是对Operation类做了一些简单的描述以及讲解,如果有不正确的地方,还请路过的朋友多加指正,更多文章请关注我的博客。
此文章由blog.csdn.net/guoyongming…
原文作者:Daniel_Coder