具有1个请求两次DispatchGroup调用完成处理程序的并行URLSession请求

问题描述 投票:0回答:1

使用DispatchGroup我正在尝试对我的客户端运行2个网络请求,当两个请求都完成时返回结果。

我有一个问题,有时DispatchGroup请求中的一个的完成处理程序被调用两次,而另一个请求根本不被调用。

一个例子是-


    func fetchProfileWithRelatedArticle(onSuccess: @escaping (User, [RelatedArticle]) -> Void, onError: @escaping (Error) -> Void) {
        let dispatchGroup = DispatchGroup()

        var user: User?
        var articles: [RelatedArticle] = []

        var errors: [Error] = []

        dispatchGroup.enter()
        fetchProfileForUser(onSuccess: {
            user = $0
            print("fetchProfile:",$0)
            print("123")
            dispatchGroup.leave()
        }, onError: { error in
            errors.append(error)
            dispatchGroup.leave()
        })

        dispatchGroup.enter()
        getArticlesForUser(onSuccess: {
            articles = $0
            print("getArticlesForUser:",$0)
            print("456")
            dispatchGroup.leave()
        }, onError: { error in
            errors.append(error)
            dispatchGroup.leave()
        })

        dispatchGroup.notify(queue: .main) {
            guard let user = user, errors.isEmpty else { return }

            onSuccess(user, articles)
        }
    }

我在这里获取用户个人资料,并获取他们撰写的文章列表。这些通过完成处理程序返回并显示在其他位置。

在大多数情况下,此方法有效,但是有时其中一个请求会调用其自己的完成处理程序两次,而另一个请求则不会。

我怀疑这可能是我的访问令牌到期的时间,因为如果我短暂离开应用程序,访问令牌将到期。我的访问令牌的寿命为2分钟。

[如果请求收到401响应,我的网络客户端中就有以下方法,该方法请求一个新令牌,然后再次调用该调用。我相信这可能无法按照我的意愿进行。

我怀疑更新对我的调度组返回的请求进行了某些处理后,再次调用了该方法。

是否可以这种方式链接请求?


            if response.statusIs401() {
                self?.refreshHandler { success in
                    guard success else { completion(.failure(TokenError.refused)); return }
                    self?.request(resource, completion)
                }
                return
            }

struct NoContent: Codable { }
typealias RefreshHandler = (@escaping (Bool) -> Void) -> ()
typealias TokenGetter = () -> [String: String]

protocol ClientType: class {
    associatedtype Route: RouterType
    func request<T: Codable>(_ resource: Route, _ completion: @escaping  (Result<T>)-> Void)
}

class Client<Route: RouterType>: ClientType {

    enum APIError: Error {
        case unknown, badResponse, jsonDecoder, other
    }

    enum TokenError: String, Error {
        case expired = "Access Token Expired"
        case refused = "Refresh Token Failed"
    }

    private(set) var session: SessionType
    private(set) var tokenGetter: TokenGetter
    private(set) var refreshHandler: RefreshHandler

    private lazy var decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
        return decoder
    }()

    init(session: SessionType, tokenGetter: @escaping TokenGetter, refreshHandler: @escaping RefreshHandler) {
        self.session = session
        self.tokenGetter = tokenGetter
        self.refreshHandler = refreshHandler
    }

    func request<T: Codable>(_ resource: Route, _ completion: @escaping  (Result<T>)-> Void) {

        let request = URLRequest(
            resource: resource,
            headers: tokenGetter()
        )

        URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
            guard error == nil else { completion(.failure(APIError.unknown)); return }
            guard let response = response as? HTTPURLResponse else { completion(.failure(APIError.badResponse)); return }

            if response.statusIs401() {
                self?.refreshHandler { success in
                    guard success else { completion(.failure(TokenError.refused)); return }
                    self?.request(resource, completion)
                }
                return
            }

            if response.statusIsSuccess() {
                guard let self = self, let data = self.deserializeNoContentResponse(data: data) else { completion(.failure(APIError.badResponse)); return }
                do {
                    let value = try self.decoder.decode(T.self, from: data)
                    DispatchQueue.main.async {
                        completion(.success(value))
                    }
                } catch let error {
                    print(error)
                }
                return
            }

            completion(.failure(APIError.other))
        }.resume()
    }

    // some calls return a 200/201 with no data
    private func deserializeNoContentResponse(data: Data?) -> Data? {

        if data?.count == 0 {
            return "{ }".data(using: .utf8)
        }

        return data
    }
}

swift grand-central-dispatch urlsession
1个回答
0
投票

听起来您在网络客户端中需要以下内容:

func makeTheRequest(_ completion: CompletionHandler) {
  URLSession.shared.dataTask(with: someURL) { data, response, error in 
    guard let httpResponse = response as? HTTPURLResponse else {
      return
    }

    if httpResponse.statusCode == 401 {
      self.refreshToken { success in
        if success { self.makeTheRequest(completion) }
      }
    }

    // handle response
    completion(whateverDataYouNeedToPass)
  }
}

这将进行调用,检查响应代码,需要刷新令牌,如果调用成功,它将调用该方法,该方法应使用首先传递给的完成处理程序再次做出响应,而无需先调用它。因此,只有在第二次调用API之后,才会调用完成处理程序。

当然,为您自己的代码采用它,应该不太难做

© www.soinside.com 2019 - 2024. All rights reserved.