2

I have an App which does hundreds of different network calls (HTTP GET requests) to a REST service. The calls are done from every single page of the app and there are much of them. But there is the requirement that two requests have to be done (on startup or awake) before any other network requests happens. The result of these two requests is some config data which is needed before all other following requests. (this requirement has many reason)

I have one central method for all GET requests. It uses AFNetworking and (of course) asynchronous handlers:

func GET(path: String, var parameters: Dictionary<String, String>? = nil) -> Future<AnyObject!> {
    let promise = Promise<AnyObject!>()

    manager.GET(path, parameters: parameters, success: { (task: NSURLSessionDataTask!, response: AnyObject!) -> Void in
        // some processing...
        promise.success(response)

    }) { (task: NSURLSessionDataTask!, error: NSError!) -> Void in
        // some failure handling...
        promise.failure(error)
    }

    return promise.future
}

The problem now is - how to do this first two calls and block all other calls until those two succeed? The obvious solution would be a semaphore which blocks the thread (not main!) until those two calls arrive successfully but if possible I want to avoid this solution. (because of deadlocks, race conditions, how to do error handling, etc... the usual suspects)

So is there any better solution for this?

The synchronous order basically has to be:

  1. 1st call
  2. wait for successful response of 1st call
  3. 2nd call
  4. wait or successful response of 2nd call
  5. allow all other calls (async) in any order

I can not do this logic on the upper layers of the app because the GET requests could come from every part of the app, so I would need to rewrite everthing. I want to do it centrally on this single GET request.

Maybe this is also possible with the Promise/Future pattern I already use, any hints are welcome.

Thanks.

Darko
  • 9,655
  • 9
  • 36
  • 48
  • 1
    using a GCD semaphore within a serial dispatch queue won't result in deadlocking or a race condition as far as I'm aware (so long as your asynchronous networking code also uses GCD). Error handling is a little more tricky, but you can fairly easily implement a timeout handler when you wait for the semaphore. I think GCD semaphores, although you don't want to use them, are probably the best way to go. – Hamish Jan 27 '16 at 19:34
  • 3
    I would look at encapsulating your network requests in NSOperations and using an NSOperationQueue. You can keep,some sort of state Boolean that indicates whether your first two calls have been made, if not then submit the desired network request with the first two calls as dependencies (and set the concurrent operations for the queue to 1). Once the first two calls are done, update the state Boolean and increase the concurrency of the operation queue – Paulw11 Jan 27 '16 at 20:04
  • 1
    Thanks for the tips! I think I will go with the NSOperationQueue to begin with. Seems less risky. – Darko Jan 27 '16 at 21:26
  • well of course it's up to you, but bear in mind that `NSOperation` carries quite a bit more overhead than GCD. – Hamish Jan 27 '16 at 22:07
  • Thanks, I will consider that. I have found a post about how to do it with GCD. http://stackoverflow.com/a/18986842/2664531 Could you explain why a serial dispatch queue is needed? Do you mean this for the two initial requests? I think I could handle this also by doing the second request in the completion handler of the first. I do not expect more then this two in the future. – Darko Jan 27 '16 at 22:13
  • @originaluser2 - `NSOperation` overhead is inconsequential in this situation. It's very well suited for these sorts of problems. – Rob Jan 27 '16 at 23:38
  • @Darko - Your example is using promises. Is there some reason why you don't use the `then` clause of the second promise to initiate the subsequent requests? But if you decide to retire promises and move to `NSOperationQueue`, [here is a class that wraps AFNetworking requests in an asynchronous operation](https://github.com/robertmryan/AFHTTPSessionOperation). – Rob Jan 28 '16 at 00:20
  • No, I will keep the promises, they are all over the code. It's the Thomvis/BrightFutures implemention. But I do not understand how to do this task with the "then" clause. Is then blocking? Could you provide a few lines of code? Thanks. – Darko Jan 28 '16 at 02:18
  • I also have problems wrapping my head around NSOperation together with the promises. The semaphore solution is clear to me with promises. It's quite easy to understand but really hard to debug (sometimes). – Darko Jan 28 '16 at 02:21
  • The problem includes also the recursive behaviour of each solution because the central GET call would have to call it self. Call 1) has to call GET to get itself and after that call 2) has also to call GET. Including the usage of promises all in all it's a quite complex problem. – Darko Jan 28 '16 at 02:29
  • What happens when I use a semaphore in GET and call the GET func recursively again from the same thread which created the semaphore? Would it result in a deadlock? Or just other threads are halted? – Darko Jan 28 '16 at 02:37
  • Hmm... but by duplicating the AFNetworking call into another func I could prevent the additional recursive complexity... that would be ok, I just want a clean and maintainable solution. – Darko Jan 28 '16 at 02:42

1 Answers1

2

There are a couple of approaches to tackle this class of problem:

  • You can use GCD (as shown in the answer you linked to) using semaphores or dispatch groups.
  • You can use asynchronous NSOperation custom subclass in conjunction with NSOperationQueue.
  • Or you can use promises.

But I'd generally suggest you pick one of these three, but don't try to introduce dispatch/operation queue patterns with your existing futures/promises code. People will suggest dispatch/operation queue approaches simply because this is how one generally solves this type of problem (using futures is not gained popular acceptance yet, and there are competing promises/futures libraries out there). But promises/futures is designed to solve precisely this problem, and if you're using that, just stick with it.

On the specifics of the dispatch/operation approaches, I could explain why I don't like the GCD approach and try to convince you to use the NSOperationQueue approach, but that's moot if you're using promises/futures.

The challenge here, though, is that you appear to be using an old version of Thomvis/BrightFutures. The current version takes two types for the generic. So my code below probably won't work for you. But I'll offer it up as as suggestion, as it may be illustrative.

For example, let's imagine that you had a loginURL (the first request), and some second request that you wanted to perform after that, but then had an array urls that you wanted to run concurrently with respect to each other, but only after the first two requests were done. Using 3.2.2 of BrightFutures, it might look like:

GET(loginURL).flatMap { responseObject -> Future<AnyObject!, NSError> in
    return self.GET(secondRequestURL)
}.flatMap { responseObject -> Future<Int, NSError> in
    return urls.map { self.GET($0) }.fold(0) { sum, _ in sum + 1 }
}.onSuccess { count in
    print("\(count) concurrent requests completed successfully")
}.onFailure { error in
    print("not successful: \(error)")
}

Judging from your code snippet, you must be using an old version of BrightFutures, so the above probably won't work as written, but hopefully this illustrates the basic idea. Use the capabilities of BrightFutures to manage these asynchronous tasks and control which are done sequentially and which are done concurrently.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks Rob, this is really useful! I didn't know that Promises/Future can do this also. But I am not sure if I need .flatMap in my case. Wouldn't be the .andThen method be the right one for me? The BrightFutures documentation also does not mention explicitly blocking and waiting. I assume that Thomas Visser presumes already an understanding of the Promises concept and the documentation is meant for those. – Darko Jan 28 '16 at 14:39
  • Yes, this would be great, I have edited the question! What I do not understand: map is used (at least in the Swift stdlib) to transforms types to other types but I don't do this. I just needed a mechanism how to return results/errors without all the callbacks hustle, so I choose Futures. But now synchronous behaviour is also needed. Thanks Rob! – Darko Jan 28 '16 at 18:12
  • Btw: why do you not recommend mixing Semaphores/NSOperationQueue with Promises? Is it a design-problem or a technical incompatibilty? I mean - eventually all of them do the same OS context switches on the lowest level, right? – Darko Jan 28 '16 at 18:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101922/discussion-between-rob-and-darko). – Rob Jan 28 '16 at 18:18