Checking for Multiple Asynchronous Responses from Alamofire and Swift

Checking for multiple asynchronous responses from Alamofire and Swift

Better than that looping process, which would block the thread, you could use dispatch group to keep track of when the requests were done. So "enter" the group before issuing each of the requests, "leave" the group when the request is done, and set up a "notify" block/closure that will be called when all of the group's tasks are done.

For example, in Swift 3:

let group = DispatchGroup()

group.enter()
retrieveDataFromURL(url1, parameters: firstParameters) {
group.leave()
}

group.enter()
retrieveDataFromURL(url2, parameters: secondParameters) {
group.leave()
}

group.notify(queue: .main) {
print("both requests done")
}

Or, in Swift 2:

let group = dispatch_group_create()

dispatch_group_enter(group)
retrieveDataFromURL(url1, parameters: firstParameters) {
dispatch_group_leave(group)
}

dispatch_group_enter(group)
retrieveDataFromURL(url2, parameters: secondParameters) {
dispatch_group_leave(group)
}

dispatch_group_notify(group, dispatch_get_main_queue()) {
print("both requests done")
}

The other approach is to wrap these requests within an asynchronous NSOperation subclass (making them cancelable, giving you control over constraining the degree of concurrency, etc.), but that's more complicated, so you might want to start with dispatch groups as shown above.

Wait for multiple Alamofire Requests to finish

I've made few changes in your code:

Network class:

func response(array: [JSON]){
print(array.count)
if(array.count == path){
self.delegate?.sendJson(array)
}
}

func getMultipleRequests(_ requests: [Int]) {
DispatchQueue.global(qos: .background).async {
let group = DispatchGroup()

var array: [JSON] = []

for request in requests {

group.enter()

self.getRequest(req: request, completion: { (json) in
array.append(json)
group.leave()
})
}

group.wait()
//this line below won't be called until all entries will leave the group
self.response(array: array)
}
}

func getRequest(req: Int, completion: @escaping (_ json: JSON) -> Void) {
path = req
let rot = Router(method: .get, path: req, parameters: nil)
Alamofire.request(rot)
.response { response in
print(response.request?.url! as Any)
// check for errors
guard response.error == nil else {
// got an error in getting the data, need to handle it
print(response.error!)
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
// make sure we got some JSON since that's what we expect
guard (response.data?.base64EncodedString()) != nil else {
print("Error: \(String(describing: response.error))")
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
guard response.response?.statusCode == 200 else{
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
let json = JSON(data: response.data!)
// get and print the title
if json != nil{
completion(json)
} else {
let errorJson: JSON = [ "Error" : "Can't get the data!"]
completion(errorJson)
return
}
}
}

So the getRequest function now have a completion block that will return a json result off each request and the function getMultipleRequests that will receive a bunch of requests from anyone

This how you can use it

Your class, that calls refresh_fiks:

@objc func refresh_fiks(){
let network = Network()
network.delegate = self
self.teams = [[]]

network.getMultipleRequests([1,2])
}

Also, instead of using group.wait() you might need to use group.notify, it's better to notify that all entries leaved the group in specified queue, like the main:

group.notify(queue: DispatchQueue.main, execute: {
print("All Done")
self.response(array: array)
})

What to read about the DispatchGroups:

RayWenderlich

ALL ABOUT SWIFT

Making multiple asynchronous HTTP requests in succession and writing with Realm

These requests will happen mostly asynchronous as you wish. But there is some synchronization happening, you might been not aware of:

  1. The response closures for Alamofire are dispatched to the main thread. So your network responses competes against any UI updates you do.
  2. Realm write transactions are synchronous and exclusive, which is enforced via locks which will block the thread where they are executed on.

In combination this both means that you will block the main thread as long as the network requests succeed and keep coming, which would also render your app unresponsive.

I'd recommend a different attempt. You can use GCD's dispatch groups to synchronize different asynchronous tasks.

In the example below, the objects are all kept in memory until they are all downloaded.

A further improvement could it be to write the downloaded data onto disk instead and store just the path to the file in the Realm object. (There are plenty of image caching libraries, which can easily assist you with that.)

If you choose a path, which depends only on the fields of PWTPhoto (or properties of the data, you can get through a quick HEAD request), then you can check first whether this path exists already locally before downloading the file again. By doing that you save traffic when updating the photos or when not all photos could been successfully downloaded on the first attempt. (e.g. app is force-closed by the user, crashed, device is shutdown)

class PTWPhotoManager {

static func downloadAllPhotos(params: [String : AnyObject], completion: (latestDate: NSDate?, photosCount: NSUInteger, error: NSError?)) {
Alamofire.request(.GET, FBPath.photos, parameters: params).responseJSON { response in
guard response.result.error == nil else {
print("error calling GET on \(FBPath.photos)")
print(response.result.error!)
completion(latestDate: nil, photosCount: 0, error: response.result.error)
return
}
if let value = response.result.value {
let json = JSON(value)
if let photos = json[FBResult.data].array {
let group = dispatch_group_create()
var persistablePhotos = [PTWPhoto](capacity: photos.count)
let manager = PTWPhotoManager()
for result in photos {
dispatch_group_enter(group)
let request = manager.downloadAndSaveJsonData(result) { photo, error in
if let photo = photo {
persistablePhotos.add(photo)
dispatch_group_leave(group)
} else {
completion(latestDate: nil, photosCount: 0, error: error!)
}
}
}

dispatch_group_notify(group, dispatch_get_main_queue()) {
let realm = try! Realm()
try! realm.write {
realm.add(persistablePhotos)
}
let latestDate = …
completion(latestDate: latestDate, photosCount: persistablePhotos.count, error: nil)
}
}
}
}
}

func downloadAndSaveJsonData(photoJSON: JSON, completion: (PTWPhoto?, NSError?) -> ()) -> Alamofire.Request {
let source = photoJSON[FBResult.source].string
let id = photoJSON[FBResult.id].string
let created_time = photoJSON[FBResult.date.createdTime].string
let imageURL = NSURL(string: source!)

print("image requested")
Alamofire.request(.GET, imageURL!).response() { (request, response, data, error) in
if let error = error {
print(error.localizedDescription)
completion(nil, error)
} else {
print("image response")
let photo = PTWPhoto()
photo.id = id
photo.sourceURL = source
photo.imageData = data
photo.createdTime = photo.createdTimeAsDate(created_time!)
completion(photo, nil)
}
}
}

}

Multiple calls using Alamofire to different APIs - how to save results to an array?

You said:

As long as Alamofire uses asynchronous calls, it is impossible to simply append new value to an array from inside of call.

You can actually go ahead and append items to the array inside the completion handlers. And because Alamofire calls its completion handlers on the main queue by default, no further synchronization is needed. And you can use dispatch groups to know when all the requests are done. First, I'd give my methods completion handlers so I know when they're done, e.g.:

func fetchFromFirstAPI(completionHandler: @escaping (Double?, Error?) -> Void) {
let APIKey = "XXXXXXXXX"
let APIURL = "http://urlapi.com/api"

let parameters: Parameters = ["APPKEY": APIKey]

Alamofire.request(APIURL, parameters: parameters).validate().responseJSON { response in
switch response.result {
case .success(let data):
let json = JSON(data)
if let result = json["main"]["value"].double {
completionHandler(result, nil)
} else {
completionHandler(nil, FetchError.valueNotFound)
}
case .failure(let error):
completionHandler(nil, error)
}
}
}

Where

enum FetchError: Error {
case valueNotFound
}

And you can then do something like:

func performRequestsAndAverageResults(completionHandler: @escaping (Double?) -> ()) {
var values = [Double]()
let group = DispatchGroup()

group.enter()
fetchFromFirstAPI { value, error in
defer { group.leave() }
if let value = value { values.append(value) }
}

group.enter()
fetchFromSecondAPI { value, error in
defer { group.leave() }
if let value = value { values.append(value) }
}

group.notify(queue: .main) {
completionHandler(self.average(values))
}
}

func average<T: FloatingPoint>(_ values: [T]) -> T? {
guard values.count > 0 else { return nil }

let sum = values.reduce(0) { $0 + $1 }
return sum / T(values.count)
}

Now, you may want to handle errors differently, but this should illustrate the basic idea: Just append the results in the completion handlers and then use dispatch groups to know when all the requests are done, and in the notify closure, perform whatever calculations you want.

Clearly, the above code doesn't care what order these asynchronous tasks finish. If you did care, you'd need to tweak this a little (e.g. save the results to a dictionary and then build a sorted array). But if you're just averaging the results, order doesn't matter and the above is fairly simple.

How to detect that all 30 Alamofire requests have all been responded to?

You can use a DispatchGroup:

let group = DispatchGroup()
var errors: [Error] = []

for date in last30Days {
group.enter()

// ...

Alamofire.request(url).responseString {
defer { group.leave() }
guard let error = response.error else {
errors.append(error)
return
}

// ...
}
}

group.notify(queue: .main) {
// All requests are finished now
}

Please note that the errors array will not be thread safe (same as your dictionary).

Edit: For thread safety, you can dispatch your updates on a queue to ensure the variables aren't modified from different threads at once.

let queue = DispatchQueue(label: "queue")
var errors: [Error] = []
for date in last30Days {
// ...
queue.async {
errors.append(error)
}
// ...
}

You can do the same for your dictionary

In Alamofire, what is the proper way to wait for a set of calls to complete?

To summarize this question and answer How to tell if blocks in loop all have completed executing?, you want to create a dispatch group, enter the group as you start each operation, exit the group as you complete each operation, and finally use display_group_notify to execute a block once the group has completed:

let group = dispatch_group_create()

foreach operation {
dispatch_group_enter(group)

startOperation(..., completion:{
dispatch_group_leave(group)
})
}

dispatch_group_notify(group, dispatch_get_main_queue()) {
// code to run when all operations complete
}

How to synchronize multiple Alamofire requests

You have to use DispatchGroup, and don't forget about deadlocks.

var data1: MyData?
var data2: MyData?
var data3: MyData?

func makeRequest(url: String, completion: (result: ResponseResult, data: MyData?) -> Void){
Alamofire.request(.GET, url).responseJSON { response in
switch response.result {
case .Success(let JSON):
completion(result: .Success, MyData(JSON))
case. Failure(let error):
completion(result: .Failure, nil)
}
}
}

let downloadGroup = DispatchGroup()

downloadGroup.enter()
downloadGroup.enter()
downloadGroup.enter()

makeRequest(url1){ result, data in
data1 = data
downloadGroup.leave()
}
makeRequest(url2){ result, data in
data2 = data
downloadGroup.leave()
}
makeRequest(url3){ result, data in
data3 = data
downloadGroup.leave()
}

DispatchQueue.global(qos: .background).async {
downloadGroup.wait()
DispatchQueue.main.async {
workWithData(data1, data2: data2, data3: data3)
}
}

Chain multiple Alamofire requests

Wrapping other asynchronous stuff in promises works like this:

func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}

Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-

Running Two Alamofire Requests in Sequence Swift

Your getToken looks more like:

func getToken(whenDone:(String?)->()) {
let httpheader: HTTPHeaders = [
"Content-Type": "application/json"
]

// Dev
let param = [params here]

Alamofire.request("url", method: .post, parameters: param, encoding: JSONEncoding.default, headers:httpheader).validate()
.responseData { response in
switch response.result {

case .success:
if let data = response.data {
let xml = SWXMLHash.parse(data)
token = (xml["authResponse"] ["authToken"].element?.text)!
whenDone(token)
}

case .failure:
print ("error")
whenDone(nil)
}
}
}

and the calling sequence just becomes:

getToken() { token ->
guard let token = token else {
return
}

getDevices()
}

Synchronise multiple web service calls in serial order in swift

Solution: Use DispatchSemaphores and a DispatchQueue

Rather than saving the closures, I decided to wrap everything up in a dispatch queue and use semaphores inside it

//Create a dispatch queue 
let dispatchQueue = DispatchQueue(label: "myQueue", qos: .background)

//Create a semaphore
let semaphore = DispatchSemaphore(value: 0)

func weatherService() {

dispatchQueue.async {
for i in 1...10 {
APIManager.apiGet(serviceName: self.weatherServiceURL, parameters: ["counter":i]) { (response:JSON?, error:NSError?, count:Int) in
if let error = error {
print(error.localizedDescription)
self.semaphore.signal()
return
}
guard let response = response else {
self.semaphore.signal()
return
}

print("\(count) ")

//Check by index, the last service in this case
if i == 10 {
print("Services Completed")
} else {
print("An error occurred")
}

// Signals that the 'current' API request has completed
self.semaphore.signal()
}

// Wait until the previous API request completes
self.semaphore.wait()
}
}
print("Start Fetching")
}

Output is this always


Sample Image



Related Topics



Leave a reply



Submit