从GCD 到 Operation

文章目录
  1. 1. Operation 的优势
  2. 2. BlockOperation
  3. 3. OperationQueue
  4. 4. 如何创建一个 AsyncOperation

Operation 的优势

Operation 底层是由 GCD 实现的,他的最大的优点是 复用性

向GCD 中的 queue 中放 task 的方式有两种

  1. 添加 block(闭包)
  2. 添加 WorkItem(对象类型)

虽然 workItem 也拥有复用性,但是用户可控性没有 Operation 好

WorkItem 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DispatchWorkItem {
public init(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], block: @escaping @convention(block) () -> Void)
public func perform()
public func wait()
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
public func notify(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], queue: DispatchQueue, execute: @escaping @convention(block) () -> Void)
// 制定 workItem 的依赖关系
public func notify(queue: DispatchQueue, execute: DispatchWorkItem)
// 取消 work,如果没有开始,那么删除这个 work
// 如果 work 执行中,那么isCancel 会被设置为 true
public func cancel()
public var isCancelled: Bool { get }
}

// 配置 item 的类型
public struct DispatchWorkItemFlags : OptionSet, RawRepresentable {
public static let barrier: DispatchWorkItemFlags
public static let detached: DispatchWorkItemFlags
public static let assignCurrentContext: DispatchWorkItemFlags
public static let noQoS: DispatchWorkItemFlags
public static let inheritQoS: DispatchWorkItemFlags
public static let enforceQoS: DispatchWorkItemFlags
}
1
2
3
4
5
6
let queue = DispatchQueue(label: "xyz") 
let backgroundWorkItem = DispatchWorkItem { }
let updateUIWorkItem = DispatchWorkItem { }

backgroundWorkItem.notify(queue: DispatchQueue.main,execute: updateUIWorkItem)
queue.async(execute: backgroundWorkItem)

对于每个 Operation 都有自己的 state

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
class Operation : NSObject { 
open func start()// 内部会调用 main方法,内部将 isExecuting = true,同步方法
open func main()// 异步需要实现的方法,用户不该自己调用这个方法,当 operation 放到queue中后,isReady = true,会有 OperationQueue 调用
open var isCancelled: Bool { get }
open func cancel()

open var isReady: Bool { get }
open var isExecuting: Bool { get }
open var isFinished: Bool { get }

open var isConcurrent: Bool { get } // 弃用
open var isAsynchronous: Bool { get } // 重写 AsyncOperation 的时候要改成 true,默认 false

open func addDependency(_ op: Operation)
open func removeDependency(_ op: Operation)
open var dependencies: [Operation] { get }

open var queuePriority: Operation.QueuePriority
open var qualityOfService: QualityOfService

open var completionBlock: (() -> Void)?
open func waitUntilFinished()

open var name: String?
}

注意:不像 gcd 你要把 gcd block or workItem 放在 queue 里才能执行
Operation 自己可以执行,不过默认情况是 sync 的 eg:

1
2
let op = SubOperation()
op.start()// 此时operation 还没有 ready好。会在当前 thread,sync 执行 Operation.main

如果想要 Operation 异步操作,需要做额外操作,eg 使用OperationQueue

BlockOperation

如果不想使用 OperationQueue 异步并发调度 Operation
可以使用 BlockOperation: Operation,执行在 default global queue.

  1. 内部管理 concurrent queue(default global queue)
  2. 内部管理 一个 group
    BlockOperation 的效果跟 GCD 中的 Group 相同
    不过内部执行是 异步并发的,NSInvocationOperation 在swift 中没有 invocation 相关方法在 swift 中都没有
1
如果 oc 想要使用 invocation 调用 swift 的方法,那么 swift 中 该方法应该 是 @objc的,响应类也要继承 NSObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let sentence = "Ray's courses are the best!"
let wordOperation = BlockOperation()
for word in sentence.split(separator: " ") {
wordOperation.addExecutionBlock {
print(word)
sleep(2)
}
}

wordOperation.completionBlock = {// group notify
print("Thank you for your patronage!")
}
duration {
wordOperation.start()
}

/* 打印结果
the
are
courses
Ray's
best!
Thank you for your patronage!*/

OperationQueue

Operation 当你使用 Queue 来管理 Operation 的时候,才能体现他真正的强大的地方,将 Operation 放到 Queue,你不需要自己调用 start,当Queue Scheduler 分配好 thread 给 Operation 的时候,就会 start,operation 的状态由系统决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open class OperationQueue : NSObject, ProgressReporting {
@available(iOS 13.0, *)
open var progress: Progress { get }

open func addOperation(_ op: Operation)
open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
open func addOperation(_ block: @escaping () -> Void)

@available(iOS 13.0, *)
open func addBarrierBlock(_ barrier: @escaping () -> Void)

open var maxConcurrentOperationCount: Int // is 1, 表示串行

open var isSuspended: Bool// 已经 executing 的 operation不会停止,后进入 queue的会suspend
open var name: String?
open var qualityOfService: QualityOfService
unowned(unsafe) open var underlyingQueue: DispatchQueue?

open func cancelAllOperations()
open func waitUntilAllOperationsAreFinished()

open class var current: OperationQueue? { get }
open class var main: OperationQueue { get }
}

在 Operation 中放一个 异步操作是可以的,但是要手动管理 state 变化,因为操作不知道什么时候 finish。
Operation 自己的state 属性又是只读的,所以重写个异步操作自己管理 state

如何创建一个 AsyncOperation

  1. 自己维护 operation 的state
  2. 重写属性,isAsynchronous = true
  3. 注意属性加锁

一个简单的 AsyncOperation 例子

source code
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
open class AsyncOperation: Operation {
enum State: String {
case ready, executing, finished
fileprivate var keyPath: String {
return "is\(rawValue.capitalized)"
}
}

private let rwlockQueue = DispatchQueue(label: "com.czw.asyncoperation", attributes: .concurrent)

private var _state: State = State.ready
var state: State {
get {
rwlockQueue.sync { return _state }
}
set {
willChangeValue(forKey: newValue.keyPath)
rwlockQueue.sync(flags: .barrier) {
_state = newValue
}
didChangeValue(forKey: state.keyPath)
}
}

override open var isReady: Bool {
return super.isReady && state == .ready
}

override open var isExecuting: Bool {
return state == .executing
}

override open var isFinished: Bool {
return state == .finished
}

// When implementing an asynchronous operation object, you must implement this property and return true.
override open var isAsynchronous: Bool {
return true
}

override open func start() {
if isCancelled {
state = .finished
return
}
main()
state = .executing
}

// call this method at main() async block
func finish() {
state = .finished
}
}
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
26
27
28
29
30
31
32
33
typealias ImageOperationCompletion = ((Data?, URLResponse?, Error?) -> Void)?
final class NetworkImageOperation: AsyncOperation {
var image: UIImage?
private let url: URL
private let completion: ImageOperationCompletion

init(url: URL, completion: ImageOperationCompletion = nil) {
self.url = url
self.completion = completion
super.init()
}

convenience init?(string: String, completion: ImageOperationCompletion = nil) {
guard let url = URL(string: string) else { return nil }
self.init(url: url, completion: completion)
}

override func main() {
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }

defer { self.state = .finished }

if let completion = self.completion {
completion(data, response, error)
return
}

guard error == nil, let data = data else { return }
self.image = UIImage(data: data)
}.resume()
}
}