简体   繁体   English

NSOperation Queue waitUntilAllOperationsAreFinished 导致 NSOperation 实例 deinit 延迟

[英]NSOperation Queue waitUntilAllOperationsAreFinished causes NSOperation instance deinit late

Our app currently using NSOperation ( Operation in Swift) to manage serials of.network request and data parsing.我们的应用程序目前使用NSOperation (Swift 中的Operation )来管理一系列网络请求和数据解析。 Some code are required to be executed after like all 5 operations in a queue are finished, which typically implemented with GCD group.一些代码需要在队列中的所有 5 个操作完成后执行,这通常使用 GCD 组实现。

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

The issue is NSOperation instance not deinit until all 5 operations done, which is causing memory release late than it supposed to.问题是NSOperation实例在完成所有 5 个操作之前不会deinit ,这导致 memory 发布晚于预期。 If queue.waitUntilAllOperationsAreFinished is removed, the instance will be deinit immediately.如果queue.waitUntilAllOperationsAreFinished被移除,实例将立即被deinit

We've added autorelease pool to avoid it.我们添加了自动释放池来避免它。 But is it possible to make NSOperation instance deinit immediately when use waitUntilAllOperationsAreFinished ?但是,是否可以在使用deinit时立即waitUntilAllOperationsAreFinished NSOperation实例?

prints with waitUntilAllOperationsAreFinished使用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

prints without waitUntilAllOperationsAreFinished无需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

The custom operation.自定义操作。

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

A better way than waiting is for example observing the operation count等待更好的方法是观察操作计数

import Combine

DispatchQueue.global().async {
     (0...5).forEach {
         queue.addOperation(CustomOperation(value: $0))
     }
 }

var store : AnyCancellable?

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

My suggestion uses Combine, it's also possible with traditional KVO我建议使用Combine,传统的KVO也可以

Didn't know about that behavior but I don't think that you can do something here.不知道这种行为,但我认为您不能在这里做某事。
If you are concerned about operation order you can set the queue to have a maxConcurrentOperationCount to 1 so that you can keep order.如果您担心操作顺序,您可以将队列的maxConcurrentOperationCount设置为 1,这样您就可以保持顺序。
If you are concerned about memory and you have some huge data you can get rid of that in the finish() method or use a completionBlock to pass it around.如果您担心 memory 并且您有一些庞大的数据,您可以在finish()方法中摆脱它或使用completionBlock来传递它。
There also the option to use KVO on the OperationQueue properties, most of its properties are KVO and KVC compliant and you can set observation on some of them to trigger a callback.还有在OperationQueue属性上使用 KVO 的选项,它的大部分属性都是 KVO 和 KVC 兼容的,您可以对其中一些设置观察以触发回调。
If you are deploying target >=13 you can use Combine as already written by vadian.如果您正在部署目标 >=13,您可以使用 vadian 已经编写的 Combine。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM