我正在创建一个
NSOperation
来延迟执行闭包。这些操作被添加到队列中,每次添加新操作之前,我都会取消队列中所有现有的操作:
let myOp = SomeOperation { [weak self] in /* do something */ }
queue.cancelAllOperations()
queue.addOperation(myOp)
final class SomeOperation: Operation {
private let closure: () -> Void
init(closure: @escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: doSomething)
}
private func doSomething() {
guard isCancelled == false else {
return
}
closure()
}
}
虽然上面的代码有效,但下面的代码却不起作用。在
DispatchQueue
闭包中,self
是 nil
:
final class SomeOperation: Operation {
private let closure: () -> Void
init(closure: @escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
guard let self = self else { return }
guard isCancelled == false else { return }
self.closure()
}
}
}
所以我想学得更深入一点:
self
是 nil
,因为一旦调用 DispatchQueue.main.asyncAfter…
,main
方法就会完成,操作就会被释放。execute: doSomething
隐式捕获/保留 self
,因此即使在 asyncAfter
之后,self
仍然存在。所以我的问题是:
start
,asynchronous
,executing
,finished
等。在我的情况下,我只需要有一个延迟,而不是实际上做任何异步的事情,应该我只在 main
中执行此操作,还是应该通过实现 Apple 建议的方法将其作为异步操作来执行?self
,这听起来不正确并且可以创建保留周期?谢谢!
你问:
- 在Apple的文档中,它说对于并发操作,我应该使用
,start
,asynchronous
,executing
等。在我的情况下,我只需要有一个延迟,而不是实际上做任何异步的事情,应该我只在 main 中执行此操作,还是应该通过实现 Apple 建议的方法将其作为异步操作执行?finished
首先,你正在做一些异步的事情。即,asyncAfter
是异步的。其次,苹果讨论并发操作背后的动机是,操作不应该在它启动的异步任务也完成之前完成。您谈到取消操作,但只有当您取消操作时操作仍在运行时,这才有意义。这个功能,将异步任务包装在一个对象中,同时不阻塞线程,是我们使用操作而不仅仅是 GCD 的关键原因之一。它为异步任务之间的各种优雅依赖关系(依赖关系、取消等)打开了大门。
关于强引用循环问题,让我们看一下你的第一个例子。虽然操作的创建者使用我的想法是否正确,在代码1中有一个隐式的自我保留,这听起来不正确并且可以创建保留周期?
[weak self]
捕获列表是谨慎的做法,但不应该是必需的。操作(或任何使用异步称为闭包的操作)的良好设计是让它在不再需要时释放闭包:
class SomeOperation2: Operation {
private var closure: (() -> Void)?
init(closure: @escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: doSomething)
}
override func cancel() {
closure = nil
super.cancel()
}
private func doSomething() {
guard !isCancelled else {
return
}
closure?()
closure = nil
}
}
这并不意味着调用者不应该使用[weak self]
捕获列表,只是操作不再需要它,并且在完成闭包时将解决任何强引用循环。[注意,在上面,为了简单起见,我省略了变量的同步。但你需要同步对它的访问以确保线程安全的设计。]
但是这种设计引出了一个问题,即为什么您要保留
asyncAfter
的计划,即使在取消操作后仍然会触发。最好取消它,通过将闭包包装在
DispatchWorkItem
中,可以取消,例如:
class SomeOperation: Operation {
private var item: DispatchWorkItem!
init(closure: @escaping () -> Void) {
super.init()
item = DispatchWorkItem { [weak self] in
closure()
self?.item = nil
}
}
override func main() {
if isCancelled { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: item)
}
override func cancel() {
item?.cancel()
item = nil
super.cancel()
}
}
概述了内存问题后,我们应该注意到,这可能毫无意义,因为您可能应该将其设为并发操作(使用所有自定义 KVO),如您在文档中确定的那样。此外,我们对取消逻辑的所有关注仅适用于异步进程完成之前操作处于活动状态的情况。因此,我们将进行并发操作。例如,
class SomeOperation: AsynchronousOperation {
private var item: DispatchWorkItem!
init(closure: @escaping () -> Void) {
super.init()
item = DispatchWorkItem { [weak self] in
closure()
self?.item = nil
self?.complete()
}
}
override func main() {
if isCancelled { return }
synchronized {
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: item)
}
}
override func cancel() {
super.cancel()
synchronized {
item?.cancel()
item = nil
}
}
}
上面使用了一个异步操作基类,它 (a) 执行必要的 KVO 通知; (b) 是线程安全的。以下是如何实现这一点的一个随机示例:
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `complete()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `complete()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `complete()` is called.
public class AsynchronousOperation: Operation {
private let lock = NSLock()
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
synchronized { _executing }
}
set {
willChangeValue(forKey: #keyPath(isExecuting))
synchronized { _executing = newValue }
didChangeValue(forKey: #keyPath(isExecuting))
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
synchronized { _finished }
}
set {
willChangeValue(forKey: #keyPath(isFinished))
synchronized { _finished = newValue }
didChangeValue(forKey: #keyPath(isFinished))
}
}
override public var isAsynchronous: Bool { return true }
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func complete() {
if isExecuting {
isExecuting = false
isFinished = true
}
}
public override func cancel() {
super.cancel()
complete()
}
override public func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
main()
}
override public func main() {
fatalError("subclasses must override `main`")
}
public func synchronized<T>(block: () throws -> T) rethrows -> T {
try lock.withLock { try block() }
}
}