Our app currently using NSOperatoin (Operation in Swift) to manage serials of network request and data parsing. Some code are required to be executed after like all 5 operations in a queue are finished, which typically implemented with GCD group.
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 casuing memory release late than it supposed to.
If queue.waitUntilAllOperationsAreFinished
is removed, the instance will be deinit immediately.
We've added autorelease pool to avoid it. But is it possible to make NSOperation instance deinit immediately when use waitUntilAllOperationsAreFinished
?
prints with 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
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")
}
}
CodePudding user response:
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
CodePudding user response:
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.
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.
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.
If you are deploying target >=13 you can use Combine as already written by vadian.