【问题标题】:Error Handling - Async Call错误处理 - 异步调用
【发布时间】:2016-03-30 05:05:24
【问题描述】:

我正在为我的项目中使用的 Web 服务创建一个框架。我已经在 GitHub 上上传了模板。 https://github.com/vivinjeganathan/ErrorHandling

它有不同的层次。第 1 层用于验证。用于形成请求的第 2 层。第 3 层用于实际的网络调用。

视图控制器 第 1 层 第 2 层 第 3 层

数据通过闭包在层之间流动,如果任何层发生错误,它需要优雅地传递给 ViewController。

我已经参考了这个链接来处理异步调用中的错误 - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/ 在同一个 repo - name - ErrorHandling-Method1 中创建了一个分支。

我能够将错误从第 3 层转移到第 2 层(单级 - 通过闭包中的函数返回响应 - 如链接中所述)。但是在跨多层传输回来时面临困难。

任何人都可以协助公共 GitHub 中提供的示例应用程序吗?

【问题讨论】:

    标签: ios swift asynchronous error-handling


    【解决方案1】:

    首先,我认为没有必要像您那样堆叠层,例如,通过将验证功能添加为层,您正在增加耦合,使该层依赖于下面的层(解析、网络等),相反,为什么不单独验证以使其仅依赖于数据?:

    class ViewController: UIViewController {
    
       var validator = InputValidator()
    
       override func viewDidLoad() {
          super.viewDidLoad()
    
          do {
             try validator.validateInput("INPUT")
             try Fetcher.requestDataWithParams("INPUT")
          }
          catch {
             handleError(error)
          }        
       }
    }
    

    现在验证功能不依赖于其他层,因此通信流程如下:

    视图控制器 ParsingLayer NetworkingLayer

    我确实重命名了图层,但不一定非要这样,您可以添加或删除图层。

    如果我尝试解释我的方法,我认为会有点复杂,所以我将使用前面的层给出一个示例,首先是底层:

    class NetworkingLayer {
       class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
          session.dataTaskWithURL(url) { (data, urlResponse, var error) in
             if let error = error {
                completion(getResult: { throw error })
             } else {
                completion(getResult: { return data })
             }
          }
       }
    }
    

    我省略了一些代码部分,但我的想法是执行任何必要的步骤以使层工作(创建会话等)并始终通过完成闭包进行通信;顶部的图层如下所示:

    class ParsingLayer {
       class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
          NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
             do {
                let data = try getResult()
                let object = try self.parseData(data)
                completion(getObject: { return object })
             }
             catch {
                completion(getObject: { throw error })
             }
          })
       } 
    } 
    

    注意完成闭包是不一样的,因为每一层都增加了功能,返回的对象可能会改变,还要注意do语句中的代码可能会以两种方式失败,首先是网络调用失败,然后是如果无法解析来自网络层的数据;同样,与顶层层的通信始终通过完成闭包完成。

    最后,ViewController 可以使用 Parsing 层在这种情况下所期望的闭包调用下一层,并且能够处理源自任何层的错误:

    override func viewDidLoad() {
       super.viewDidLoad()
       do {
          try validator.validateInput("INPUT")
          try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
          do {
             let object = try getObject()
             try self.validator.validateOutput(object)
             print(object)
          }
          catch {
             self.handleError(error)
          }
       })
       catch {
          handleError(error)
       }        
    }
    

    请注意,在完成闭包中有一个do catch,这是必要的,因为调用是异步进行的,现在响应已经通过所有层并且实际上已经更改为更专业的类型,你甚至可以无需为验证功能制作层即可验证结果。

    希望对你有帮助。

    【讨论】:

    • 感谢您的详细解释。我喜欢您消除稍后进行验证的方法。但是这个地方,let data = try getResult()[In PARSING LAYER] & let object = try getObject() [In View Controller] 不是很方便。我个人觉得 NSNotification 更干净,更简单。你能说出你的方法比通知有什么优势吗?因为我没有找到。我很高兴知道它并相应地重构我的代码。
    • 这种情况下解析层依赖于网络层,通信是1对1的关系,确实没有必要使用广播系统,使用通知的一些缺点是: - 没有编译时间来检查以确保观察者正确处理通知。 - 不是很可追溯(难以调试) - 增加了另一层复杂性 - 通知名称和 UserInfo 字典键需要观察者和控制器都知道 - 由于每个人都可以订阅通知,因此数据的封装可能很差
    • 我应该说我在几个应用程序中的答案中使用了这种方法,我有一个网络层与解析层通信,解析层与最终使用的存储库层通信由表示层(View Controllers)和服务层组成;网络层仅处理与服务器的通信,解析层使用自省和泛型将 JSON 响应转换为模型对象,存储库层处理 URL 和参数,所有数据和错误通过链没有任何问题,没有通知。
    • handler 可能看起来很复杂,但最初只是看起来和理解很复杂,最终不再是一个包含另一个闭包的闭包,实际上并没有太多开销,因为每一层都接收一个闭包(作为引用传递),每层只有两个调用:首先调用完成以发送带有结果或错误的闭包,然后下一层调用该闭包以接收结果或错误;另一方面,您使用结果或错误调用通知中心,NC 执行搜索并再次调用侦听器(层)
    • 此外,我不确定,但我认为使用通知会导致系统内聚度低
    【解决方案2】:

    如果你从不抛出甚至试图捕捉,为什么要声明你的方法抛出?您可以使用 throwable 声明在所有层中抛出错误,甚至可以更改每个级别的 throwable 类型。

    更新:没有想到在异步操作中抛出不工作。使用 NSNotification 是一种不错的方法,或者您也可以查看RXSwift 或类似的方法来解决它。我个人的建议是使用 RxSwift。这可以让你远离回调地狱,你目前正在进入。

    【讨论】:

    • 这是不可能的,如果你的闭包是异步的 ;)
    【解决方案3】:

    您正确地发现了异步代码中错误处理的一个严重问题。

    使用同步函数似乎很容易——它只返回一个错误代码,或者有一个额外的错误参数,或者使用新的 Swift throws 语法。这是一个同步函数:

    func computeSome() throws -> Some
    

    这是异步函数的可行函数签名:

    func computeSomeAsync(completion: (Some?, NSError?) -> ())
    

    异步函数返回Void并且不抛出。如果失败,它会使用错误参数集调用其完成函数。

    但是,完成处理程序很快就会变得很麻烦,尤其是在嵌套代码中。

    解决办法是使用一个Future

    func computeSomeAsync() -> Future<Some>
    

    这个函数是异步的,不会抛出 - 并返回一个 Future。那么,未来是什么?

    未来代表异步函数的最终结果。当您调用异步函数时,它会立即返回,并且您会得到一个 placeholder 作为结果。这称为future,最终将由计算值的底层后台任务完成

    当底层任务最终成功时,这个未来包含函数的计算值。当它失败时,它将包含错误。

    根据Future Library的实际实现和API,可以通过注册continuations获得结果:

    let future = computeSomeAsync()
    
    future.onSuccess { value in
        print("Value: \(value)")
    }
    
    
    future.onFailure { error in
        print("Error: \(error)")
    }
    

    一开始可能看起来很奇怪,但你可以用期货做一些很棒的事情:

    fetchUser(id).flatMap { user in
        fetchProfileImage(user.profileImageUrl).flatMap { image in
            cacheImage(image)
        }
    }
    .onFailure { error in
        print("Something went wrong: \(error)")
    }
    

    上述语句是异步的 - 以及函数fetchUserfetchProfileImagecacheImage。包括错误处理。

    【讨论】:

    • 谢谢。但我并没有完全理解你的方法(未来的功能及其优势)。如果您不介意,您可以更改我在 github 中的代码,以便我可以使用。
    • @vivin 你可以克隆FutureLib 并阅读自述文件。在 Xcode 中打开项目并创建一个新的 Playgrounds 文件并尝试在 Playground 中重现和理解简单示例。查看现有的 Playground 以获取模拟网络请求的示例函数。也可以看看BrightFutures,这是另一个非常棒的 Scala 风格的期货和承诺的实现。
    【解决方案4】:

    我个人会使用传递 NSError 的通知作为层中通知的对象,并在视图控制器中观察通知。

    在层中:

    NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error)
    

    在视图控制器中

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil)
    

    选择器方法:

    func errorEncountered(notification: NSNotification!) {
        let error: NSError! = notification.object as! NSError
        NSLog("error: \(error)")
    }
    

    【讨论】:

    • 你为什么要发布通知而不是捕捉到抛出的错误?
    猜你喜欢
    • 2016-12-13
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2020-05-24
    • 2018-07-29
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多