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

早过忘川 提交于 2019-12-19 04:06:23

问题


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.


回答1:


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.




回答2:


Never mind I figured it out :)

Setting the finished status to true and false triggers the KVO of NSOperationQueue and hence either starts or cancels the operation of dependent operations.

If I want my operation to execute at any point whether the dependency operations finished successfully or not I can't make use of finished flag. In this case finished flag should always be true to indicate operation finished its execution.

But how will I ensure the management of decency chain for those operations that have dependencies set and really depends whether the previous operation was successful or not ?? Simple I added another variable called finishedSuccessfully to my NSOperationSubclass.

When a operation fails though it sets the finished flag to true it will set finishedSuccessfully to false. This will cause the dependent operations start method to get called.

In the start method of dependent operation, it will iterate over all the dependency operations and checks has all of them finished with finishedSuccessfully = true.

If yes that means all the dependency operations have finished execution and finished it successfully so it can start execution. On the other hand if any one of them has finishedSuccessfully = false, that means operation finished executing but failed to do whatever it was supposed to do hence this operation should also stop itself and inform its dependents that it finished with finishedSuccessfully = false.

Summary :

  1. Operations will execute only after the execution of all the dependency operations finished executing no matter whether they finished successfully or not.

  2. Operations which are actually concerned about the execution status of their dependency operations will check for the status and then finally decides whether to continue execution or not.

Dependency chain is maintained as well as the confirmation that sync operation executes as well :)

Here is my implementation :

import UIKit
import CoreData
import SwiftyJSON

class VMBaseOperation: NSOperation {
    var finishedSuccessfully : Bool = false
    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
        }
        //those operations which actually wants to know if all its dependency operations finished successfully or not can create a subclass of this class override start method and add the below code
        for operation in self.dependencies {
            if (operation as! VMBaseOperation).finishedSuccessfully == false {
                self.operationFailed()
                return
            }
        }
        //others can ignore.
        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.finishedSuccessfully = false
        self.operationCompleted()
    }

    func operationSucceeded(){
        self.finishedSuccessfully = true
        self.operationCompleted()
    }
}


来源:https://stackoverflow.com/questions/39201284/executing-the-nsoperation-when-all-other-operations-in-nsoperationqueue-finished

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!