2

I want to send requests one after the other. Wait for the response from the first to send the second.

Currently I use this:

DispatchQueue.global().async {
            let dispatchGroup = DispatchGroup()

            dispatchGroup.enter()
            self.requestOne { _ in
                dispatchGroup.leave()
            }
            dispatchGroup.wait()


            dispatchGroup.enter()
            self.requestTwo { _ in
                dispatchGroup.leave()
            }
            dispatchGroup.wait()


            dispatchGroup.enter()
            self.requestTree { _, url in
                user.profileImage = url?.absoluteString
                dispatchGroup.leave()
            }
            dispatchGroup.wait()


            self.requestFour { error in
                completion(error)
            }
        }

It works very well but I wanted to know if there is a way to make this cleaner, swiftier without using

dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.wait()

Even a pattern to wrap logic in a class

Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46
  • 1
    I wouldn't recommend hand rolling something like this. Use a better abstraction, like a Promise (such as from PromiseKit) or observable (such as from Combine or RxSwift) – Alexander Feb 10 '20 at 14:14
  • Possibly helpful: https://stackoverflow.com/questions/50026983/how-can-you-use-dispatch-groups-to-wait-to-call-multiple-functions-that-depend-o – Martin R Feb 10 '20 at 14:49

2 Answers2

2

Instead of calling dispatchGroup.leave() in the requests completion, why don't you just call the next request there:

self.requestOne() { [weak self] _ in
   guard let `self` = self else { return }
   self.requestTwo() { [weak self] _ in
      guard ..
      self.requestThree...
   }
}

Or as suggested in a comment, use a framework like RxSwift or Combine

EDIT:
When some requests will be called on others not, based on different conditions, you could check the condition in your requestXX method. If condition isn't met, just call completion and return.

func requestTwoIfNeeded(paramsToCheckCondition, completion: ...) {
   guard conditionMet else {
      completion(state)
      return
   }
   self.requestTwo(completion)
}

func sequentialRequests(completion: ...) {
   self.requestOne() { [weak self] _ in
      guard let `self` = self else { return }
      self.requestTwoIfNeeded(conditionalParams) { [weak self] _ in
         ...
      }
   }
}
Robin Schmidt
  • 1,153
  • 8
  • 15
2

A simple network layer utilizing Combine

This answer is heavily influenced by this post. I removed unnecessary code where possible and added code where needed to compose a working playground.

Api unrelated layer parts

import Foundation
import Combine

struct Agent {

    func run<T: Decodable>(_ request: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<T, Error> {
        return URLSession.shared
            .dataTaskPublisher(for: request)
            .tryMap { result -> T in
                return try decoder.decode(T.self, from: result.data)
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

Api related layer parts

enum GithubAPI {
    static let agent = Agent()
    static let base = URL(string: "https://api.github.com")!
}

extension GithubAPI {

    static func repos(username: String) -> AnyPublisher<[Repository], Error> {
        let request = URLRequest(url: base.appendingPathComponent("users/\(username)/repos"))
        return agent.run(request)
    }

    struct Repository: Codable {
        let node_id: String
        let name: String
    }

    static func issues(repo: String, owner: String) -> AnyPublisher<[Issue], Error> {
        let request = URLRequest(url: base.appendingPathComponent("repos/\(owner)/\(repo)/issues"))
        return agent.run(request)
    }

    struct Issue: Codable {
        let url: URL
    }

}

Actual clean and swifty code you are looking for

Note that issue request depends on repo request. Actual requests are triggered with sink call.

let user = "Alamofire"
let repos = GithubAPI.repos(username: user)
let firstRepo = repos.compactMap { $0.first }
let issues = firstRepo.flatMap { repo in
    GithubAPI.issues(repo: repo.name, owner: user)
}
let token = issues.sink(receiveCompletion: { (_) in }) { (issues) in
    print(issues)
}
Lausbert
  • 1,471
  • 2
  • 17
  • 23