我使用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
}
}