我正在创建一个NSOperation,它会延迟执行一个闭包.操作被添加到队列中,每次添加新操作之前,我都会取消队列中的所有现有操作:

let myOp = SomeOperation { [weak self] in /* do something */ }
queue.cancelAllOperations()
queue.addOperation(myOp)

操作代码1

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闭包中,selfnil:

操作代码2

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()
        }
    }
}

所以我正在试着更深入地了解:

  1. 在代码2中,selfnil,因为只要调用DispatchQueue.main.asyncAfter…,main方法就会结束,从而释放操作.
  2. 代码1之所以有效,是因为execute: doSomething隐式捕获/保留self,因此即使在asyncAfter之后,self仍然存在.

所以我的问题是:

  1. 在苹果的文档中,它说对于并发操作,我应该使用startasynchronousexecutingfinished等.在我的情况下,我只需要有一个延迟,而不是实际上做任何异步操作,我应该只在main中做这件事,还是应该通过实现苹果建议的那些方法作为异步操作来做?
  2. 我的 idea 正确吗,在代码1中有一个隐式保留的self,这听起来不正确,可以创建保留循环?

谢谢!

推荐答案

你问:

  1. 在苹果的文档中,它说对于并发操作,我应该使用startasynchronousexecutingfinished等.在我的情况下,我只需要有一个延迟,而不是实际上做任何异步操作,我应该只在Main中做,还是应该通过实现苹果建议的方法作为异步操作来做?

首先,你are在做一些不同步的事情.即,asyncAfter是异步的.

其次,苹果讨论并发操作背后的动机是,在它启动的异步任务也完成之前,操作不应该结束.您说要取消操作,但只有当您要取消操作时操作仍在运行时,这才有意义.这个特性,在不阻塞线程的情况下将异步任务包装在一个对象中,这是我们使用操作而不仅仅是GCD的关键原因之一.它为异步任务之间的各种优雅依赖打开了大门(依赖、取消等).

  1. 我的 idea 正确吗,在代码1中,有一个隐含的self 保留,这听起来不正确,可以创建保留循环?

关于强引用周期问题,让我们看一下您的第一个示例.虽然操作的创建者使用[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.synchronized { try block() }
    }
}

extension NSLocking {
    public func synchronized<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

Ios相关问答推荐

使用音频单元播放音频流,出现爆裂声

Firebase SDK 10+—无法安装软件包

通过在SWIFT中不起作用的泛型传递协议一致性类型

如何将Safari调试器附加到Turbo-iOS原生应用中的模拟器

iOS 17-在Xcode 15中添加导航控制器时,导航栏不会立即显示

清除/删除 NavigationController 中的多个 ViewController

当 Swift 枚举具有 any existential 作为其关联值之一时,我如何使它符合 `Equatable`?

将 URLCache 子类与 URLSession 一起使用

iOS SwiftUI - 从以前的 struct 调用函数

用溢出的长文本对齐 Flutter 中的行和列

如何在字段包含字典数组的firestore上查询?

使用 CIImage 支持的 UIImage 设置 UIImageView 时发生罕见的崩溃

快速禁用 UITextfield 的用户输入

在 Swift 中追加字符串

如何在 iOS 中判断暗模式?

Xcode / iOS模拟器:手动触发重大位置更改

快速从 NSTimeInterval 转换为小时、分钟、秒、毫秒

在 iOS 上没有显示数字键盘

UIGestureRecognizer 阻止子视图处理touch 事件

zoom UIButton 动画 - Swift