ios - OperationQueue 上的 `progress` 可以使用异步操作吗?

标签 ios swift progress operation operationqueue

从 iOS13 开始,可以使用 progress 属性监控 OperationQueue 的进度。文档指出,在跟踪进度时,只有不覆盖 start() 的操作才算数。但是,根据文档,异步操作必须覆盖start()并且不调用super()

这是否意味着 asynchronous 操作和 progress 是互斥的(即只有同步操作可以与 progress 一起使用)?如果是这种情况,这似乎是一个巨大的限制。

在我自己的项目中,我删除了对 start() 的重写,所有出现都正常工作(例如,依赖项仅在 isFinished 时启动> 在我的异步操作基类内部的依赖操作上设置为 true)。但是,这似乎有风险,因为 Operation 明确声明要覆盖 start()

想法?

文档引用:

https://developer.apple.com/documentation/foundation/operationqueue/3172535-progress

By default, OperationQueue doesn’t report progress until totalUnitCount is set. When totalUnitCount is set, the queue begins reporting progress. Each operation in the queue contributes one unit of completion to the overall progress of the queue for operations that are finished by the end of main(). Operations that override start() and don’t invoke super don’t contribute to the queue’s progress.

https://developer.apple.com/documentation/foundation/operation/1416837-start

If you are implementing a concurrent operation, you must override this method and use it to initiate your operation. Your custom implementation must not call super at any time. In addition to configuring the execution environment for your task, your implementation of this method must also track the state of the operation and provide appropriate state transitions.

更新:我最终放弃了我的 AysncOperation,换成了一个简单的 SyncOperation,它一直等到 finish() 被调用(使用信号量)。

/// A synchronous operation that automatically waits until `finish()` is called.
open class SyncOperation: Operation {

    private let waiter = DispatchSemaphore(value: 0)

    /// Calls `work()` and waits until `finish()` is called.
    public final override func main() {
        work()
        waiter.wait()
    }

    /// The work of the operation. Subclasses must override this function and call `finish()` when their work is done.
    open func work() {
        preconditionFailure("Subclasses must override `work()` and call `finish()`")
    }

    /// Finishes the operation.
    ///
    /// The work of the operation must be completed when called. Failing to call `finish()` is a programmer error.
    final public func finish() {
        waiter.signal()
    }
}

最佳答案

你问:

Does this mean asynchronous operations and progress are mutually exclusive (i.e. only synchronous operations can be used with progress)? This seems like a massive limitation if this is the case.

是的,如果你实现start,你必须自己将操作的子Progress添加到队列的父progress。 (令人惊讶的是,他们没有通过观察 isFinished KVO 让基本操作更新进度,但事实就是如此。或者他们可以使用 becomeCurrent(withPendingUnitCount: )-resignCurrent 模式,那么这种脆弱的行为就不会存在了。)

但我不会仅仅因为您想要它们的Progress 就放弃异步操作。通过使您的操作同步,您将在操作期间不必要地占用数量非常有限的工作线程之一。这是一种看起来非常方便的决定,可能不会立即带来问题,但长期可能会带来在您意外耗尽工作线程池时极难识别的问题。

幸运的是,添加我们自己的 child Progress 非常简单。考虑一个自定义操作,它有自己的子 Progress:

class TestOperation: AsynchronousOperation {
    let progress = Progress(totalUnitCount: 1)

    override func main() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
            progress.completedUnitCount = 1
            finish()
        }
    }
}

然后,在将它们添加到您的队列时,将操作的 progress 添加为操作队列的 Progress 的子项:

class ViewController: UIViewController {
    @IBOutlet weak var progressView: UIProgressView!

    let queue: OperationQueue = ...

    override func viewDidLoad() {
        super.viewDidLoad()

        queue.progress.totalUnitCount = 10
        progressView.observedProgress = queue.progress

        for _ in 0 ..< 10 {
            queue.progress.becomeCurrent(withPendingUnitCount: 1)
            queue.addOperation(TestOperation())
            queue.progress.resignCurrent()
        }
    }
}

将您自己的、自定义的、异步的、Operation 子类的 Progress 添加到操作队列的 Progress 是微不足道的。或者,您可能只是创建自己的父 Progress 并完全绕过 OperationQueueprogress。但无论哪种方式,它都非常简单,没有必要将婴儿(异步自定义 Operation 子类)连同洗澡水一起扔掉。


如果需要,您可以进一步简化调用点,例如,为使用 Progress 的操作定义 typealias:

typealias ProgressOperation = Operation & ProgressReporting

extension OperationQueue {
    func addOperation(progressOperation: ProgressOperation, pendingUnitCount: Int64 = 1) {
        progress.addChild(progressOperation.progress, withPendingUnitCount: pendingUnitCount)
        addOperation(progressOperation)
    }
}

class TestOperation: AsynchronousOperation, ProgressReporting {
    let progress = Progress(totalUnitCount: 1)

    override func main() { ... }
}

然后在添加操作时:

queue.progress.totalUnitCount = 10
progressView.observedProgress = queue.progress

for _ in 0 ..< 10 {
    queue.addOperation(progressOperation: TestOperation())
}

关于ios - OperationQueue 上的 `progress` 可以使用异步操作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70920717/

相关文章:

ios - 如何在 NSLog 中打印 NSRange

xcode - 在 Swift 中以编程方式设置 Action 监听器

json - Swift 从函数完成中获取值并将其设置为全局变量

swift - 为什么同一 SKNode 层中的对象不相互交互?

android - 保存游戏进度的最佳位置在哪里?

jquery - 可以使用 xhrFields 将 onprogress 功能添加到 jQuery.ajax() 中吗?

android - Phonegap 数据库选项、IndexedDB、SQLite?

ios - Swift 项目中 Objective-C 的条件编译

ios - 来自 xib 的自定义 View 通过界面生成器添加到 Controller - 保留自动布局约束并通过它们调整大小

mysql - 进度条