【问题标题】:Executing the NSOperation when all other operations in NSOperationQueue finished no matter whether they finished successfully or not当 NSOperationQueue 中的所有其他操作完成时执行 NSOperation,无论它们是否成功完成
【发布时间】:2016-08-29 08:02:30
【问题描述】:

你好,我这里有一个奇怪的情况:

概览:

我正在开发一个用户可以启动多个操作的应用程序,所有这些操作都将在后台线程上运行,因此不会阻塞 UI。其中一些操作相互依赖,而另一些则不依赖。 因此,为了确保操作仅在所有必要的依赖操作完成执行后才执行,我使用了 Operation 的依赖属性。我正在使用异步操作。

这是我的实现:

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

它的作用:

每个操作都接受一个 Web 请求并尝试从服务器获取数据,如果失败,它会尝试 3 次,然后通过调用 operationFailed 方法最终将其完成状态设置为 false 并停止所有相关操作的执行另一方面,如果成功,它会通过调用operationCompleted 将其完成状态更改为true,从而触发剩余依赖操作的执行。

问题是什么:

依赖就像一种魅力。没有问题。现在我需要在操作队列中的所有操作执行完成后从服务器同步数据,无论它们是否成功完成。

最简单的方法是创建一个操作来同步来自服务器的数据,并将其作为依赖操作添加到添加到 operationQueue 的所有操作中。

但是由于上述操作的性质,即使一个操作全部失败,它也会停止执行所有相关操作(如预期的那样),但是因为我来自服务器操作的同步数据也是一项相关操作,所以它永远不会执行即使一个操作失败:(

我需要什么:

在维护我上面提到的依赖关系的同时,我需要知道当操作队列中的所有操作完成执行时,无论它们是成功还是失败,如何执行一个操作来同步来自服务器的数据。

这是否可能:(请帮帮我。提前谢谢。

【问题讨论】:

    标签: ios swift nsoperation nsoperationqueue


    【解决方案1】:

    没关系,我想出来了:)

    将完成状态设置为 true 和 false 会触发 NSOperationQueue 的 KVO,从而启动或取消依赖操作的操作。

    如果我希望我的操作在任何时候执行,无论依赖操作是否成功完成,我都不能使用完成标志。在这种情况下,完成标志应始终为真,以指示操作已完成执行。

    但是我将如何确保那些设置了依赖项并且真正取决于之前的操作是否成功的操作的体面链管理?很简单,我在我的 NSOperationSubclass 中添加了另一个名为 finishedSuccessfully 的变量。

    当一个操作失败时,虽然它把完成标志设置为真,但它会将finishedSuccessfully设置为假。这将导致依赖的操作启动方法被调用。

    在依赖操作的start方法中,它会遍历所有的依赖操作,并检查它们是否都以finishedSuccessfully = true结束。

    如果是,则表示所有依赖操作都已完成执行并成功完成,因此可以开始执行。另一方面,如果其中任何一个已经finishedSuccessfully = false,这意味着操作已完成执行但未能完成它应该做的任何事情,因此该操作也应该停止自己并通知其依赖它以finishedSuccessfully = false完成。

    总结:

    1. 操作只有在所有依赖操作执行完成后才会执行,无论它们是否成功完成。

    2. 真正关心其依赖操作执行状态的操作会检查状态,然后最终决定是否继续执行。

    维护依赖链以及同步操作执行的确认:)

    这是我的实现:

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

    【讨论】:

      【解决方案2】:

      随着你的实现operationFailed:

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

      你破坏了 NSOperation 的原生逻辑:

      操作依赖

      依赖是一种在特定环境中执行操作的便捷方式 命令。您可以使用 addDependency: 和 removeDependency: 方法。默认情况下,一个操作 具有依赖关系的对象直到其所有 相关操作对象已完成执行。一旦最后 依赖操作完成,但是操作对象变为 准备好并能够执行。

      NSOperation 支持的依赖不区分 依赖操作是否成功完成。 (换句话说,取消操作类似地将其标记为 完成。)由您决定是否使用 在其依赖操作的情况下应继续进行依赖 被取消或未成功完成任务。这可能 要求您加入一些额外的错误跟踪功能 进入你的操作对象。

      按照设计,如果操作失败,它应该完成。但它可以以某种方式标记自己(一些特殊属性或 cancelled 之一)。

      相关操作应该检查是否可以启动。像下面这样的东西应该可以完成这项工作:

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

      这里我们覆盖ready 属性来确定应该做什么。如果requireDependencesCompletiontrue,则操作将检查其所有依赖关系,如果其中一个被取消,则将自行取消。

      requireDependencesCompletion 设置为true 用于您的典型操作,并将false 设置为您的障碍 操作,以便在任何情况下都会启动。

      【讨论】:

      • 绝对正确 :D 我发布了我的答案,因为我纠正了我的错误 :) 虽然我结束了创建一个额外的变量finishedSuccessfully,但在你使用取消的属性时这是多余的:)
      • 虽然我不喜欢使用 cancelled 来实现自定义逻辑,因此我使用了我自己的 finishedSuccessfully 自定义标志 :) 但是覆盖 cancel 的概念非常好 :) 谢谢你的回答 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-23
      • 2012-01-17
      • 1970-01-01
      相关资源
      最近更新 更多