Our app currently using NSOperation
(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 is causing 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")
}
}
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.