John Boris
John Boris

Reputation: 25

How can I combine the result of these 2 async methods?

I have 2 methods I call. I need to produce a model that contains the result of both and call another method.

I wanted to avoid placing 1 method inside another as this could expand out to 3 or 4 additional calls.

Essentially once I have the results for setUserFollowedState and loadFollowersForTopic I want to send both values to another function.

Coming from a JS land I would use async/await but this does not exist in Swift.

  func setUserFollowedState() {
    following.load(for: userID, then: { [weak self, topic] result in
      guard self != nil else { return }
      let isFollowed = (try? result.get().contains(topic)) ?? false
      // do something with isFollowed?

    })
  }

  func loadFollowersForTopic() {
    followers.load(topic, then: { [weak self, topic] result in
      guard self != nil else { return }
      let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0
      // do something with count?
    })
  }

Upvotes: 1

Views: 1296

Answers (2)

Crt Gregoric
Crt Gregoric

Reputation: 402

Another approach (which I believe is a bit cleaner) would be to use a DispatchGroup to combine the result of the mentioned methods.

You would modify your original methods to take a completion handler and then combine the two results where you actually need the data. See example below.

func setUserFollowedState(completion: @escaping ((Bool) -> Void)) {
  following.load(for: userID, then: { [weak self, topic] result in
    guard self != nil else { return }
    let isFollowed = (try? result.get().contains(topic)) ?? false
    // Call completion with isFollowed flag
    completion(isFollowed)
  })
}

func loadFollowersForTopic(completion: @escaping ((Int) -> Void)) {
  followers.load(topic, then: { [weak self, topic] result in
    guard self != nil else { return }
    let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0
    // Call completion with follower count
    completion(count)
  })
}

func loadFollowedAndCount() {
    let group = DispatchGroup()

    var isFollowed: Bool?
    // Enter group before triggering data fetch
    group.enter()
    setUserFollowedState { followed in
        // Store the fetched followed flag
        isFollowed = followed
        // Leave group only after storing the data
        group.leave()
    }

    var followCount: Int?
    // Enter group before triggering data fetch
    group.enter()
    loadFollowersForTopic { count in
        // Store the fetched follow count
        followCount = count
        // Leave group only after storing the data
        group.leave()
    }

    // Wait for both methods to finish - enter/leave state comes back to 0
    group.notify(queue: .main) {
        // This is just a matter of preference - using optionals so we can avoid using default values
        if let isFollowed = isFollowed, let followCount = followCount {
            // Combined results of both methods
            print("Is followed: \(isFollowed) by: \(followCount).")
        }
    }
}

Edit: always make sure that a group.enter() is followed by a group.leave().

Upvotes: 1

kbunarjo
kbunarjo

Reputation: 1375

You can store both async call results as optional properties. When your callbacks happen, set these properties then check that both properties have been set. If they've been set, you know both async calls have returned.

private var isFollowed: Bool?
private var count: Int?

func setUserFollowedState() {
    following.load(for: userID, then: { [weak self, topic] result in
        guard let self = self else { return }
        let isFollowed = (try? result.get().contains(topic)) ?? false

        self.isFollowed = isFollowed
        performPostAsyncCallFunctionality()
    })
}

func loadFollowersForTopic() {
    followers.load(topic, then: { [weak self, topic] result in
        guard let self = self else { return }
        let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0

        self.count = count
        performPostAsyncCallFunctionality()
    })
}

private func performPostAsyncCallFunctionality() {
    // Check that both values have been set.
    guard let isFollowed = isFollowed, let count = count else { return }

    // Both calls have returned, do what you need to do.
}

The good thing about this approach is that you can easily add more async calls using the pattern. However, if you need to make that many async network calls at once, I would recommend you think about rewriting your server-side logic so you only need one network call for this functionality.

Upvotes: 2

Related Questions