Sandeep Bhandari Sandeep Bhandari - 3 months ago 41
Swift Question

Executing the NSOperation when all other operations in NSOperationQueue finished no matter whether they finished successfully or not

Hi I have a strange situation here :

OverView:

I am working on an app where user can initiate multiple operations and all these operations will run on a background thread hence will not block the UI. Some of these operations are dependent on each other and some are not.
So in order to ensure that operation will execute only after all the necessary dependencies operations finished executing am using the dependency property of Operation. I am making use of Asynchronous operations.

Here is my implementation :

import UIKit
import CoreData
import SwiftyJSON

class VMBaseOperation: NSOperation {
var finishedStatus : Bool = false
var executionStatus : Bool = false
var retryCount : Int = 0
private (set) var requestToQueue : BaseRequest? = nil
var vmOperationCompletionBlock: ((JSON?) -> Void)?
var vmOperationFailureBlock: ((WebResponseError?) -> Void)?

override init() {
super.init()
}

convenience init(withVMRequest request : BaseRequest) {
self.init()
requestToQueue = request
}

override func start() {
if self.cancelled {
self.finished = true
return
}
NSThread.detachNewThreadSelector(#selector(main), toTarget: self, withObject: nil)
self.executionStatus = true
}


override func main() {
if self.cancelled {
return
}
self.hitWebService()
}

func hitWebService(){
let webserviceManager = WebServiceManager()
webserviceManager.getResponseFromRequest(requestToQueue!) { (requset, response, data, error) in
let error = WebResponseError.checkResponse(response, request: requset, error: error)
if error != nil {
if error == WebResponseError.NO_INTERNET {
if self.vmOperationFailureBlock != nil {
self.vmOperationFailureBlock!(error)
}
self.operationFailed()
}
else{
self.retryCount += 1
if self.retryCount == 3 {
if self.vmOperationFailureBlock != nil {
self.vmOperationFailureBlock!(error)
}
self.operationFailed()
}
else{
self.hitWebService()
}
}
}
else{
if data == nil {
self.retryCount += 1
if self.retryCount == 3 {
if self.vmOperationFailureBlock != nil {
self.vmOperationFailureBlock!(nil)
}
self.operationFailed()
}
else{
self.hitWebService()
}
}
else{
let json = JSON(data: data!)
if self.vmOperationCompletionBlock != nil {
self.vmOperationCompletionBlock!(json)
}
self.operationCompleted()
}
}
}
}

override var finished: Bool {
get{
return finishedStatus
}
set{
self.willChangeValueForKey("isFinished")
finishedStatus = newValue
self.didChangeValueForKey("isFinished")
}
}

override var executing: Bool {
get{
return executionStatus
}
set{
self.willChangeValueForKey("isExecuting")
executionStatus = newValue
self.didChangeValueForKey("isExecuting")
}
}


override var asynchronous: Bool{
get{
return true
}
set{
self.willChangeValueForKey("isAsynchronous")
self.asynchronous = true
self.didChangeValueForKey("isAsynchronous")
}
}

func operationCompleted(){
self.executing = false
self.finished = true
}

func operationFailed(){
self.executing = false
self.finished = false
}
}


What It Does :

each operation takes a web request and attempts to get the data from server and if it fails, it tries 3 times before finally setting its finished status to false by calling
operationFailed
method and there by stopping all the dependent operation from executing forever.On the other hand if it succeeds it changes its finished status to true by calling
operationCompleted
hence triggers the execution of remaining dependent operations.

What is the Issue:

Dependency works like a charm. No issue with that. Now I need to sync the data from server when all the operations in the operation queue finished executing no matter whether they finished successfully or not.

Easiest way to do it is to create a Operation to sync the data from server and add it as dependent operation to all the operations added to operationQueue.

But because of the above mentioned nature of the operation, even if one operation fails all its stops the execution of all the dependent operations (as expected) but because my sync data from server operation is also a dependent operation it will never execute even if one operation fails :(

What I need :

While maintaining the dependency I mentioned above I need to know how to execute an operation to sync the data from server when all the operations in operation queue finishes executing no matter whether they succeed or fail.

Is that even possible :( Please help me out . Thanks in advance.

Avt Avt
Answer

With your implementation operationFailed:

func operationFailed(){
    self.executing = false
    self.finished = false
}

you break NSOperation's native logic:

Operation Dependencies

Dependencies are a convenient way to execute operations in a specific order. You can add and remove dependencies for an operation using the addDependency: and removeDependency: methods. By default, an operation object that has dependencies is not considered ready until all of its dependent operation objects have finished executing. Once the last dependent operation finishes, however, the operation object becomes ready and able to execute.

The dependencies supported by NSOperation make no distinction about whether a dependent operation finished successfully or unsuccessfully. (In other words, canceling an operation similarly marks it as finished.) It is up to you to determine whether an operation with dependencies should proceed in cases where its dependent operations were cancelled or did not complete their task successfully. This may require you to incorporate some additional error tracking capabilities into your operation objects.

By design if operation fails it should finish. But it could mark itself somehow (some special property or a cancelled one).

Dependent operations should check is it possible to start. Something like the following should do the job:

var requireDependencesCompletion: Bool = true

override var ready: Bool { 
    if requireDependencesCompletion
    {
        for op in self.dependencies {
            if op.cancelled {
                cancel()
        }
    }
    super.ready
}

Here we override ready property to determine what should be done. If requireDependencesCompletion is true an operation will check all its dependences and cancel itself if one of them was cancelled.

Set requireDependencesCompletion to true for your typical operations and to false to your barrier operation so it will start in any case.

Comments