NSOperation Queue waitUntilAllOperationsAreFinished 导致 NSOperation 实例 deinit 延迟

NSOperation Queue waitUntilAllOperationsAreFinished causes NSOperation instance deinit late

我们的应用目前使用 NSOperation(Swift 中的 Operation)来管理网络请求和数据解析的序列。 一些代码需要在队列中的所有 5 个操作完成后执行,通常使用 GCD 组实现。

DispatchQueue.global().async {
    (0...5).forEach(){
        self.queue.addOperation(CustomOperation(value: [=10=]))
    }
    self.queue.waitUntilAllOperationsAreFinished()
    print("All Tasks Done")
}

问题是 NSOperation 实例不是 deinit 直到完成所有 5 个操作,这导致内存释放比预期的晚。 如果 queue.waitUntilAllOperationsAreFinished 被删除,实例将立即 deinit

我们添加了自动释放池来避免它。但是是否可以在使用 waitUntilAllOperationsAreFinished 时立即创建 NSOperation 实例 deinit?

打印 waitUntilAllOperationsAreFinished

Begin Task 5
Begin Task 4
Begin Task 3
Begin Task 2
Begin Task 1
Begin Task 0
Finish Task 0
Finish Task 1
Finish Task 2
Finish Task 3
Finish Task 4
Finish Task 5
deinit 0
deinit 1
deinit 2
deinit 3
deinit 4
deinit 5
All Tasks Done

不打印 waitUntilAllOperationsAreFinished

All Tasks Done
Begin Task 0
Begin Task 1
Begin Task 4
Begin Task 3
Begin Task 5
Finish Task 0
Begin Task 2
deinit 0
Finish Task 1
deinit 1
Finish Task 2
deinit 2
Finish Task 3
deinit 3
Finish Task 4
deinit 4
Finish Task 5
deinit 5

自定义操作。

class CustomOperation: Operation {
    
    public enum State {
        case ready
        case running
        case finished
    }

    private var state: State = .ready
    
    override var isAsynchronous: Bool { return true }
    
    override open var isExecuting: Bool { state == .running }

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

    var value: Int = 0
    
    init(value: Int) {
        super.init()
        
        self.value = value
    }
    
    override func main() {
        print("Begin Task \(value)")
        DispatchQueue.global().asyncAfter(deadline: .now()+DispatchTimeInterval.seconds(value)) {
            print("Finish Task \(self.value)")
            self.finish()
        }
    }
    
    func finish() {
        willChangeValue(forKey: "isExecuting")
        willChangeValue(forKey: "isFinished")
        state = .finished
        didChangeValue(forKey: "isFinished")
        didChangeValue(forKey: "isExecuting")
    }
    
    deinit {
        print("deinit")
    }
}

等待更好的方法是例如观察操作计数

import Combine

DispatchQueue.global().async {
     (0...5).forEach {
         queue.addOperation(CustomOperation(value: [=10=]))
     }
 }

var store : AnyCancellable?

store = queue.publisher(for: \.operationCount)
    .sink { value in
        if value == 0 { print("All Tasks Done")}
    }

我建议用Combine,传统KVO也可以

不知道这种行为,但我认为您不能在这里做某事。
如果您担心操作顺序,您可以将队列的 maxConcurrentOperationCount 设置为 1,这样您就可以保持顺序。
如果您关心内存并且您有一些巨大的数据,您可以在 finish() 方法中摆脱它或使用 completionBlock 来传递它。
还有在 OperationQueue 属性上使用 KVO 的选项,它的大部分属性都符合 KVO 和 KVC 标准,您可以对其中一些设置观察以触发回调。
如果您正在部署目标 >=13,您可以使用 vadian 已经编写的 Combine。