【问题标题】:Swift "retry" logic on request应要求提供快速“重试”逻辑
【发布时间】:2016-04-26 15:28:18
【问题描述】:

所以当我的上传请求失败时,我对如何实现重试逻辑有点迷茫。

这是我的代码,我想要一些关于如何操作的指导

func startUploading(failure failure: (NSError) -> Void, success: () -> Void, progress: (Double) -> Void) {
        DDLogDebug("JogUploader: Creating jog: \(self.jog)")

        API.sharedInstance.createJog(self.jog,
            failure: { error in
                failure(error)
            }, success: {_ in
                success()
        })
    }

【问题讨论】:

标签: swift alamofire


【解决方案1】:

这是一个通用解决方案,可应用于任何没有参数的异步函数,回调函数除外。我通过只有successfailure 回调来简化逻辑,progress 应该不难添加。

所以,假设你的函数是这样的:

func startUploading(success: @escaping () -> Void, failure: @escaping (Error) -> Void) {
    DDLogDebug("JogUploader: Creating jog: \(self.jog)")

    API.sharedInstance.createJog(self.jog,
        failure: { error in
            failure(error)
        }, success: {_ in
            success()
    })
}

匹配的retry 函数可能如下所示:

func retry(times: Int, task: @escaping(@escaping () -> Void, @escaping (Error) -> Void) -> Void, success: @escaping () -> Void, failure: @escaping (Error) -> Void) {
    task(success, 
        { error in
            // do we have retries left? if yes, call retry again
            // if not, report error
            if times > 0 {
                retry(times - 1, task: task, success: success, failure: failure)
            } else {
                failure(error)
            }
        })
}

并且可以这样调用:

retry(times: 3, task: startUploading,
    success: {
        print("Succeeded")
    },
    failure: { err in
        print("Failed: \(err)")
})

如果一直失败,上面将重试startUploading调用3次,否则将在第一次成功时停止。

编辑。具有其他参数的函数可以简单地嵌入到闭包中:

func updateUsername(username: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) {
    ...
}

retry(times: 3, { success, failure in updateUsername(newUsername, success, failure) },
    success: {
        print("Updated username")
    },
    failure: {
        print("Failed with error: \($0)")
    }
)

更新 retry 函数声明中的太多 @escaping 子句可能会降低其可读性,并增加使用函数时的认知负担。为了改善这一点,我们可以编写一个具有相同功能的简单通用结构:

struct Retrier<T> {
    let times: UInt
    let task: (@escaping (T) -> Void, @escaping (Error) -> Void) -> Void
    
    func callAsFunction(success: @escaping (T) -> Void, failure: @escaping (Error) -> Void) {
        let failureWrapper: (Error) -> Void = { error in
            // do we have retries left? if yes, call retry again
            // if not, report error
            if times > 0 {
                Retrier(times: times - 1, task: task)(success: success, failure: failure)
            } else {
                failure(error)
            }
        }
        task(success, failureWrapper)
    }
    
    func callAsFunction(success: @escaping () -> Void, failure: @escaping (Error) -> Void) where T == Void {
        callAsFunction(success: { _ in }, failure: failure)
    }
}

由于是可调用的,结构体可以像普通函数一样被调用:

Retrier(times: 3, task: startUploading)(success: { print("success: \($0)") },
                                        failure: { print("failure: \($0)") })

,或者可以通过app进行循环:

let retrier = Retrier(times: 3, task: startUploading)
// ...
// sometime later
retrier(success: { print("success: \($0)") },
        failure: { print("failure: \($0)") })

【讨论】:

  • 请问“retry(3, { success, failure in updateUsername(newUsername, success, failure) }”中的“in”是什么意思?谢谢。
  • @allenlinli in 这里代表Swift 在闭包参数和它的主体之间的分隔符
【解决方案2】:

更新到 swift 5,使用 Result 类型而不是成功和失败块。

func retry<T>(_ attempts: Int, task: @escaping (_ completion:@escaping (Result<T, Error>) -> Void) -> Void,  completion:@escaping (Result<T, Error>) -> Void) {

    task({ result in
        switch result {
        case .success(_):
            completion(result)
        case .failure(let error):
            print("retries left \(attempts) and error = \(error)")
            if attempts > 1 {
                self.retry(attempts - 1, task: task, completion: completion)
            } else {
                completion(result)
            }
        }
    })
}

这就是我们如何使用重试功能:

func updateUser(userName: String) {
retry(3, task: { (result) in
    startUploadingWithResult(userName: userName, completion: result)
}) { (newResult) in
    switch newResult {
    case .success(let str):
        print("Success : \(str)")
    case .failure(let error):
        print(error)
    }
  }
}

 updateUser(userName: "USER_NAME")

【讨论】:

    【解决方案3】:

    这是 swift 3 的更新答案。我还在成功块中添加了一个通用对象,因此如果您在网络调用完成后创建一个对象,您可以将其传递给最终的闭包。这里是重试函数:

    func retry<T>(_ attempts: Int, task: @escaping (_ success: @escaping (T) -> Void, _ failure: @escaping (Error) -> Void) -> Void, success: @escaping (T) -> Void, failure: @escaping (Error) -> Void) {
    task({ (obj) in
      success(obj)
    }) { (error) in
      print("Error retry left \(attempts)")
      if attempts > 1 {
        self.retry(attempts - 1, task: task, success: success, failure: failure)
      } else {
          failure(error)
        }
      }
    }
    

    如果您更新了一个用户并想用更新后的信息取回一个新的用户对象,您将如何使用它:

    NetworkManager.shared.retry(3, task: { updatedUser, failure in
    NetworkManager.shared.updateUser(user, success: updatedUser, error: failure) }
    , success: { (updatedUser) in
      print(updatedUser.debugDescription)
    }) { (err) in
      print(err)
    }
    

    【讨论】:

    • 嗨贾斯汀,可以用这个函数:func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask 谢谢
    猜你喜欢
    • 1970-01-01
    • 2020-03-12
    • 1970-01-01
    • 2016-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 2011-04-09
    相关资源
    最近更新 更多