How to make an internal asynchronous request request complete first before completing an external asynchronous request request in Swift?

I have been trying to achieve this for some time and cannot make it work.

First let me show you a simple code example:

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() } 

Basically, I execute an asynchronous request in my methodOne , and inside this function I call my methodTwo , which executes another asynchronous request.

The problem I am facing is that when methodTwo called methodTwo it never enters an asynchronous session. However, in methodTwo , enter an asynchronous session, but only once k = someArray.count - 1 . He is basically in line until the very end, which I do not want to achieve.

Here's an example 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 want the async request from methodTwo complete at each iteration before the async request from methodOne .

Here is an example output of my goal:

 Inside Async1 k = 0 Calling Async2 Inside Async2 Inside Async1 k = 1 Calling Async2 Inside Async2 Inside Async1 ... 

I found something similar here: Wait for the first async function to complete, then execute the second async function

However, I could not get this to work with suggestions and solutions.

Can someone point me in the right direction?

thanks

+1
source share
5 answers

One way to do this is to modify methodTwo() to accept the callback as an argument, then you can use the semaphore:

 func methodOne(urlString1: String) { let targetURL = NSURL(string: urlString1) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) { data, response, error in let queue = dispatch_queue_create("org.myorg.myqueue", nil) dispatch_async(queue) { // DO STUFF j = some value print("Inside Async1") for k in j...someArray.count - 1 { print("k = \(k)") print("Calling Async2") dispatch_semaphore_t sem = dispatch_semaphore_create(0); self.methodTwo("some url string") { dispatch_semaphore_signal(sem); } dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } } } task.resume() } func methodTwo(urlString2: String, callback: (() -> ())) { let targetURL = NSURL(string: urlString2) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) { data, response, error in // DO STUFF print("inside Async2") callback() } task.resume() } 

Note that in order not to block the delegate queue of the methodOne callback, the example creates its own queue, which you can block at will.

+1
source

You must use a synchronous request. It is easy to use with this extension:

 extension NSURLSession { public static func requestSynchronousData(request: NSURLRequest, completion: ((data: NSData?, error: NSError?) -> Void)?) { var data: NSData? = nil var error: NSError? = nil let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0) NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { taskData, _, taskError -> () in data = taskData error = taskError if data == nil, let error = error {print(error)} dispatch_semaphore_signal(semaphore); }).resume() dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) completion?(data: data, error: error) } } 

and send a synchronous request to methodTwo :

 func methodOne(urlString1: String) { guard let targetURL = NSURL(string: urlString1) else { return } let request = NSURLRequest(URL: targetURL) NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in // DO STUFF print("Inside Async1") for k in 0..<5 { print("k = \(k)") print("Calling Async2") self.methodTwo("http://www.google.com") } }.resume() } func methodTwo(urlString2: String) { guard let targetURL = NSURL(string: urlString2) else { return } let request = NSURLRequest(URL: targetURL) NSURLSession.requestSynchronousData(request) { (data, error) in // DO STUFF print("inside Async2") } } 

You can also manage it using the Send Queue . More on GCD

+1
source

Instead of semaphores or groups that others have reported (which blocks a thread, which can be problematic if you have too many threads blocked), I would use a custom asynchronous subclass of NSOperation for network requests. After you wrapped the request in asynchronous NSOperation , you can add a bunch of operations to the operation queue without blocking the threads, but enjoying the dependencies between these asynchronous operations.

For example, a network operation might look like this:

 class NetworkOperation: AsynchronousOperation { private let url: NSURL private var requestCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())? private var task: NSURLSessionTask? init(url: NSURL, requestCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) { self.url = url self.requestCompletionHandler = requestCompletionHandler super.init() } override func main() { task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in self.requestCompletionHandler?(data, response, error) self.requestCompletionHandler = nil self.completeOperation() } task?.resume() } override func cancel() { requestCompletionHandler = nil super.cancel() task?.cancel() } } /// Asynchronous Operation base class /// /// This class performs all of the necessary KVN of `isFinished` and /// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer /// a concurrent NSOperation subclass, you instead subclass this class which: /// /// - must override `main()` with the tasks that initiate the asynchronous task; /// /// - must call `completeOperation()` function when the asynchronous task is done; /// /// - optionally, periodically check `self.cancelled` status, performing any clean-up /// necessary and then ensuring that `completeOperation()` is called; or /// override `cancel` method, calling `super.cancel()` and then cleaning-up /// and ensuring `completeOperation()` is called. public class AsynchronousOperation : NSOperation { override public var asynchronous: Bool { return true } private let stateLock = NSLock() private var _executing: Bool = false override private(set) public var executing: Bool { get { return stateLock.withCriticalScope { _executing } } set { willChangeValueForKey("isExecuting") stateLock.withCriticalScope { _executing = newValue } didChangeValueForKey("isExecuting") } } private var _finished: Bool = false override private(set) public var finished: Bool { get { return stateLock.withCriticalScope { _finished } } set { willChangeValueForKey("isFinished") stateLock.withCriticalScope { _finished = newValue } didChangeValueForKey("isFinished") } } /// Complete the operation /// /// This will result in the appropriate KVN of isFinished and isExecuting public func completeOperation() { if executing { executing = false finished = true } } override public func start() { if cancelled { finished = true return } executing = true main() } } // this locking technique taken from "Advanced NSOperations", WWDC 2015 // https://developer.apple.com/videos/play/wwdc2015/226/ extension NSLock { func withCriticalScope<T>(@noescape block: Void -> T) -> T { lock() let value = block() unlock() return value } } 

Having done this, you can initiate a series of requests that can be executed sequentially:

 let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 1 for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in // do something with the `data` } queue.addOperation(operation) } 

Or, if you do not want to suffer from a significant decrease in the performance of consecutive requests, but still want to limit the degree of concurrency (to minimize system resources, avoid timeouts, etc.), you can set maxConcurrentOperationCount to a value of 3 or 4.

Or you can use dependencies, for example, to start a process when all asynchronous downloads are executed:

 let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 3 let completionOperation = NSBlockOperation() { self.tableView.reloadData() } for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in // do something with the `data` } queue.addOperation(operation) completionOperation.addDependency(operation) } // now that they're all queued, you can queue the completion operation on the main queue, which will only start once the requests are done NSOperationQueue.mainQueue().addOperation(completionOperation) 

And if you want to cancel requests, you can easily cancel them:

 queue.cancelAllOperations() 

Operations is an incredibly rich mechanism for managing a series of asynchronous tasks. If you refer to the WWDC 2015 Advanced NSOperations video , they took this template to a completely different level with conditions and observers (although their solution may be a bit reworked for simple tasks. IMHO).

+1
source

Here is an approach that I have already suggested in other answers to the simalar question, specially designed for your problem:

Your methods method1 and method2 are asynchronous. Asynchronous functions must have a means to complete the signal for the caller. One approach for this is to use completion handlers:

  func method1(url: NSURL, completion: (Result1?, ErrorType?) -> ()) func method2(url: NSURL), completion: (Result2?, ErrorType?) -> ()) 

Here Result1 and Result2 are the computed result of asynchronous functions. Because the task may fail, the signature of the completion handler has a means to return the calculated value or error.

Suppose your first method1 method evaluates a list of elements, each of which contains a different URL. For each URL in this list, you want to call method2 .

Wrap these linked tasks in a new method function (it is also asynchronous and therefore also has a completion handler!):

 func method(completion: (Result?, ErrorType?)-> ()) { let url = ... self.method1(url) { (result1, error) in if let result = result1 { // `result` is an array of items which have // a url as property: let urls = result.map { $0.imageUrl } // Now, for each url, call method2: // Use a dispatch group in order to signal // completion of a group of asynchronous tasks let group = dispatch_group_create() let finalResult: SomeResult? let finalError: ErrorType? urls.forEach { imageUrl in dispatch_group_enter(group) self.method2(imageUrl) { (result2, error) in if let result = result2 { } else { // handle error (maybe set finalError and break) } dispatch_group_leave(group) } } dispatch_group_notify(dispatch_get_global_queue(0,0)) { completion(finalResult, finalError) } } else { // Always ensure the completion handler will be // eventually called: completion(nil, error) } } } 

The above approach uses a scheduling group to group a number of tasks. When a task begins, the number of group tasks increases with dispatch_enter . When the task is completed, the number of tasks in the group will be reduced using dispatch_group_leave .

When the group is empty (all tasks are completed), the block sent using dispatch_group_notify will be executed in the specified queue. We use this block to call the exit handler of the external method function.

You can be creative in handling errors. For example, you can simply ignore the failure of the second method2 method and continue to get the result, or you can cancel all the tasks that are still running and return an error. You can also resolve success and failure when calling method2 and composing an array of "result" as finalResult so that the group finalResult and finalResult -, which contains a detailed result about each call.

You may have noticed that there is no way to cancel the task. Well no. This will require cancellation of tasks. There are elegant solutions to this problem, but this is beyond the scope of this answer.

+1
source
-2
source

Source: https://habr.com/ru/post/1241376/


All Articles