I've been trying to achieve this for some time and cannot get it to work.
First let me show a simple sample code:
override func viewDidLoad()
{
super.viewDidLoad()
methodOne("some url bring")
}
func methodOne(urlString1: String)
{
let targetURL = NSURL(string: urlString1)
let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) {(data, response, error) in
// DO STUFF
j = some value
print("Inside Async1")
for k in j...someArray.count - 1
{
print("k = \(k)")
print("Calling Async2")
self.methodTwo("some url string")
}
}
task.resume()
}
func methodTwo(urlString2: String)
{
let targetURL = NSURL(string: urlString2)
let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) {(data, response, error) in
// DO STUFF
print("inside Async2")
}
task.resume()
}
What I'm basically doing is I perform an asynchronous request within my methodOne
, and within that function, I call my methodTwo
which performs another asynchronous request.
The problem I'm having is that when methodTwo
is called, it never enters the asynchronous session. It does however, within methodTwo
, enter the asynchronous session, but only once k = someArray.count - 1
. It's basically queuing up until the very end, which is not what I'd like to achieve.
Here's a sample output:
Inside Async1
k = 0
Calling Async2
Inside Async1
k = 0
Calling Async2
k = 1
Calling Async2
Inside Async1
k = 0
Calling Async2
k = 1
Calling Async2
k = 2
Calling Async2
Inside Async1
.....
Inside Async1
k = 0
Calling Async2
k = 1
Calling Async2
k = 2
Calling Async2
k = 3
Calling Async2
k = 4
Inside Async2
In other words, I'd like to have the async request from methodTwo
complete on each iteration before the async request from methodOne
completes.
Here's a sample output of what my goal is:
Inside Async1
k = 0
Calling Async2
Inside Async2
Inside Async1
k = 1
Calling Async2
Inside Async2
Inside Async1
...
I've found something similar here: Wait until first async function is completed then execute the second async function
However, I couldn't get this to work with the suggestions and solutions.
Can someone point me in the right direction?
Thanks
Instead of semaphores or groups that others have advised (which blocks a thread, which can be problematic if you have too many threads blocked), I would use a custom, asynchronous
NSOperation
subclass for network requests. Once you've wrapped the request in an asynchronousNSOperation
, you can then add a bunch of operations to an operation queue, not blocking any threads, but enjoying dependencies between these asynchronous operations.For example, a network operation might look like:
Having done that, you can initiate a whole series of requests that could be performed sequentially:
Or, if you don't want to suffer the significant performance penalty of sequential requests, but still want to constrain the degree of concurrency (to minimize system resources, avoid timeouts, etc), you can set
maxConcurrentOperationCount
to a value like 3 or 4.Or, you can use dependencies, for example to trigger some process when all of the asynchronous downloads are done:
And if you want to cancel the requests, you can easily cancel them:
Operations are incredibly rich mechanism for controlling a series of asynchronous tasks. If you refer to WWDC 2015 video Advanced NSOperations, they have taken this pattern to a whole other level with conditions and observers (though their solution might be a bit overengineered for simple problems. IMHO).
Take a look at dispatch groups as in this article: https://www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build-dispatch-groups.html
You should use synchronous request. It's easy to use with this extension:
and send synchronous request in
methodTwo
:Also you can manage it using Dispatch Queue. Learn more about GCD
Here is the approach I already suggested in another answers for a simalar question, specifically tailored for your problem:
Your methods
method1
andmethod2
are both asynchronous. Asynchronous functions should have a means to signal completion to the caller. One approach for this is using completion handlers:Here,
Result1
andResult2
is the computed result of the asynchronous functions. Since a task may fail, the signature of the completion handler has a means to either return the computed value or an error.Suppose, your first method
method1
evaluates a list of items, each containing another URL. For each URL in this list, you want to callmethod2
.Wrap these composed tasks into a new function
method
(it's also asynchronous, and thus it also has a completion handler as well!):The above approach uses a dispatch group in order to group a number of tasks. When a task starts, the group number of tasks will be increased using
dispatch_enter
. When a task will be completed, the number of tasks in the group will be decreased withdispatch_group_leave
.When the group is empty (all tasks have completed) the block submitted with
dispatch_group_notify
will be executed on the given queue. We use this block in order to call the completion handler of the outer functionmethod
.You can be creative regarding handling the errors. For example, you might want to just ignore a failure of second method
method2
and continue to get a result, or you might want to cancel every task which is still running and return an error. You could also allow to have success and failure when callingmethod2
and compose an array of "result" asfinalResult
, let the group succeed and returnfinalResult
- which maintains a detailed result about each call.You may have noticed, that there is no means to cancel a task. Yes, there isn't. This would require cancelable tasks. There are elegant solutions for this problem too, but this beyond this answer.
One way to do this is to change
methodTwo()
to accept a callback as an argument, then you can use a semaphore:Note that to not block the delegate queue of methodOne's task callback, the example creates its own queue that you can block at will.