我正在尝试在一个辅助项目中使用Operation
,而不是在我的网络代码中散布基于闭包的回调以帮助消除嵌套调用。所以我正在阅读这个主题,我遇到了this实现:
open class AsynchronousOperation: Operation {
// MARK: - Properties
private let stateQueue = DispatchQueue(label: "asynchronous.operation.state", attributes: .concurrent)
private var rawState = OperationState.ready
private dynamic var state: OperationState {
get {
return stateQueue.sync(execute: {
rawState
})
}
set {
willChangeValue(forKey: "state")
stateQueue.sync(flags: .barrier, execute: {
rawState = newValue
})
didChangeValue(forKey: "state")
}
}
public final override var isReady: Bool {
return state == .ready && super.isReady
}
public final override var isExecuting: Bool {
return state == .executing
}
public final override var isFinished: Bool {
return state == .finished
}
public final override var isAsynchronous: Bool {
return true
}
// MARK: - NSObject
private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> {
return ["state"]
}
private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return ["state"]
}
private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return ["state"]
}
// MARK: - Foundation.Operation
public final override func start() {
super.start()
if isCancelled {
finish()
return
}
state = .executing
execute()
}
// MARK: - Public
/// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
open func execute() {
fatalError("Subclasses must implement `execute`.")
}
/// Call this function after any work is done or after a call to `cancel()` to move the operation into a completed state.
public final func finish() {
state = .finished
}
}
@objc private enum OperationState: Int {
case ready
case executing
case finished
}
这个Operation
子类的一些实现细节我想帮助理解。
stateQueue
属性的目的是什么?我看到它被get
计算属性的set
和state
使用,但我找不到任何解释sync:flags:execute
和sync:execute
方法的文档他们使用。
NSObject
部分中返回["state"]
的三个类方法的目的是什么?我没有看到它们被用在任何地方。我在NSObject
,class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>
找到了,但这似乎并不能帮助我理解为什么会声明这些方法。
答案 0 :(得分:34)
你说:
- 醇>
stateQueue
财产的目的是什么?我看到它被state
计算属性的get和set使用,但我找不到任何解释他们使用的sync:flags:execute
和sync:execute
方法的文档。
此代码&#34;同步&#34;访问属性以使其线程安全。关于你为什么需要这样做,请参阅the Operation
documentation,建议:
多核注意事项
...当你继承
NSOperation
时,必须确保所有被覆盖的方法都可以安全地从多个线程调用。如果在子类中实现自定义方法(如自定义数据访问器),则还必须确保这些方法是线程安全的。因此,必须同步对操作中的任何数据变量的访问,以防止潜在的数据损坏。有关同步的更多信息,请参阅Threading Programming Guide。
关于这个并发队列用于同步的确切用法,这被称为&#34;读写器&#34;图案。读写器模式的这个基本概念是读取可以相互发生并发生(因此sync
,没有障碍),但写入必须永远不会同时执行该属性的任何其他访问(因此带障碍的async
。这些都在WWDC 2012视频Asynchronous Design Patterns with Blocks, GCD, and XPC中有所描述。请注意,虽然该视频概述了基本概念,但它使用较旧的dispatch_sync
和dispatch_barrier_async
语法,而不是使用仅sync
和async(flags: .barrier)
语法的Swift 3及更高版本语法这里。
您还问:
- 返回
醇>NSObject
的{{1}}部分中三个类方法的目的是什么?我没有看到它们被用在任何地方。我在["state"]
,NSObject
找到了,但这似乎无法帮助我理解为什么声明这些方法。
这些只是确保对class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>
属性的更改触发属性isReady
,isExecuting
和isFinished
的KVN的方法。这三个密钥的KVN对于异步操作的正确运行至关重要。无论如何,Key-Value Observing Programming Guide: Registering Dependent Keys中概述了这种语法。
您找到的state
方法是相关的。您可以使用该方法注册相关密钥,也可以使用原始代码段中显示的各个方法。
BTW,这是您提供的keyPathsForValuesAffectingValue
课程的修订版,即:
您不得致电AsynchronousOperation
。正如start
documentation所说(强调补充):
如果要实现并发操作,则必须覆盖此方法并使用它来启动操作。 您的自定义实施不得随时致电
super.start()
。
在Swift 4中添加super
。
重命名@objc
以使用execute
,这是main
子类的惯例。
将Operation
声明为isReady
属性是不合适的。任何子类都应该有权进一步细化其final
逻辑(尽管我们很少这样做)。
使用isReady
使代码更安全/更健壮。
使用#keyPath
属性时,您不需要手动操作KVN。在此示例中,不需要手动调用dynamic
和willChangeValue
。
更改didChangeValue
,使其仅finish
移至.finished
州。
因此:
isExecuting
答案 1 :(得分:2)
在使用Rob's answer中更新的代码段时,应该意识到由此更改引起的错误的可能性:
- 更改完成,使其仅在isExecuting时才进入.finished状态。
以上内容与Apple docs不符:
除了在取消操作时简单退出之外,将取消的操作移至适当的最终状态也很重要。具体来说,如果您自己管理完成属性和执行属性的值(可能是因为您正在执行并发操作),则必须相应地更新这些属性。具体来说,您必须将finish返回的值更改为YES,将执行返回的值更改为NO。即使必须先取消操作,也必须进行这些更改。
在某些情况下会导致错误。例如,如果“ maxConcurrentOperationCount = 1”的操作队列获得3个异步操作A B和C,则如果在A期间取消所有操作,则C将不会执行,并且队列将卡在操作B上。
答案 2 :(得分:1)
关于你的第一个问题:stateQueue在你的操作状态写入新值时锁定你的操作:
return stateQueue.sync(execute: {
rawState
})
和
stateQueue.sync(flags: .barrier, execute: {
rawState = newValue
})
由于您的操作是异步的,因此在读取或写入一个状态之前,可以调用另一个状态。就像你想写的是执行,但同时已经完成了已完成的调用。因此,要避免这种情况,stateQueue会将操作状态锁定为读取和写入,直到完成其上一次调用。它的工作像Atomic。而是使用调度队列,您可以使用NSLock的扩展来简化从https://developer.apple.com/videos/play/wwdc2015/226/的WWDC 2015 https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip中的高级NSOperations示例代码执行关键代码,您可以实现如下:
private let stateLock = NSLock()
private dynamic var state: OperationState {
get {
return stateLock.withCriticalScope{ rawState }
}
set {
willChangeValue(forKey: "state")
stateLock.withCriticalScope {
rawState = newValue
}
didChangeValue(forKey: "state")
}
}
关于你的第二个问题:它是一个只读属性isReady,isExecuting,isFinished的KVO通知来管理操作状态。您可以阅读:http://nshipster.com/key-value-observing发布到最后,以便更好地了解KVO。