Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
403 views
in Technique[技术] by (71.8m points)

ios - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...