Synchronous Alamofire Request with DispatchGroup - ios

I need to wait until Alamofire request finishes getting data. (Error or value). I'm calling Alamofire function inside a for loop in another function so that Alamofire requests should be finished before second for loop called. For example; first loop -> first request -> second loop -> second request...so on. Now It goes first loop -> second loop -> and after all loops finishes requests response is turning.
Request Function:
func like(sender_id: String, completion: #escaping (String?) -> ()){
let group = DispatchGroup()
if let key = api_key{
let headers: HTTPHeaders = [
"X-Auth-Token": key,
"Accept": "application/json"
]
group.enter()
Alamofire.request(baseUrl + likeURL + sender_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
.responseJSON { (response) -> Void in
guard response.result.isSuccess else {
print("Error while doing : \(response.result.error)")
completion(nil)
group.leave()
return
}
if let error = response.error{
completion(nil)
group.leave()
print("Error Occured with Request: \(error)")
}
if let jsonData = response.data{
let json = JSON(data: jsonData)
print(json)
group.leave()
if let matched = json["match"].bool{
completion(matched.description)
print("Liked!: \(matched)")
if(matched){
}
}else{
group.leave()
"There is an error with JSON"
}
}}
}
}
Where I call:
func like_all(completion: #escaping() -> ()){
for user in SharedUsers.sharedUser.users{
if let id = user.id{
Network.sharedInstance.like(sender_id: id) { result in
print(result)
}
}
else{
continue
}
}
completion()
}

You are using dispatch group, obviously with the intent on waiting for the group at the end of the function. But you're not waiting for it, so you're not getting the synchronous behavior you were looking for.
But that's good, because if you wait for that group (or semaphore, the other pattern to achieve this behavior) on the main thread, not only will you be blocking the main thread (which results in horrible UX and risk having your app killed by watchdog process), you're going to deadlock because responseJSON uses the main queue for it's completion handler. So before you add the wait() call on your dispatch group/semaphore, make sure you dispatch this whole for loop asynchronously to some background thread. That avoids blocking the main thread and eliminates the deadlock risk.
But this whole pattern is fundamentally flawed, as you really shouldn't use dispatch groups or semaphores to make it synchronous at all. That raises a few questions:
The first question is why you want to make this synchronous. Network requests have inherent latency, so you performing a sequence of them will be very slow. Only do this sequence of requests if you absolutely have to (e.g. each request cannot be formed because it needs something from the response of the prior request). But that doesn't appear to be the case here. So why make this process unnecessary so.
But let's assume for a second that you absolutely have to perform these sequentially (not true here, from what I can tell, but let's tease this out). Then there are two patterns for performing a series of requests consecutively rather than concurrently:
You can either lose this for loop entirely, and simply have a routine that sends the n-th request and sends request n+1 in its completion handler. That completely eliminates the need for dispatch groups/semaphores to block a thread.
Or you can wrap this in operation (e.g. https://stackoverflow.com/a/27022598/1271826) and use operation queue.

I solve it by calling like function with index + 1 everytime Alamofire returns value. I also call function in completion part of fetch request.
Here is the code:
#objc func action(_ sender: LGButton) {
sender.titleString = "Started to Like :)"
Network.sharedInstance.get_rec(completion: { (result) in
if(result != nil)
{
Network.sharedInstance.like(sender: 0, completion: { (result) in
//print(result)
})
}
})
}
Like Function:
func like(sender: Int, completion: #escaping (String?) -> ()){
if let key = api_key{
let headers: HTTPHeaders = [
"X-Auth-Token": key,
"Accept": "application/json"
]
print(sender)
print(SharedUsers.sharedUser.users.count)
if(sender < SharedUsers.sharedUser.users.count){
if let user_id = SharedUsers.sharedUser.users[sender].id{
Alamofire.request(baseUrl + likeURL + user_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
.responseJSON { (response) -> Void in
guard response.result.isSuccess else {
print("Error while doing : \(response.result.error)")
completion(nil)
return
}
if let error = response.error{
completion(nil)
print("Error Occured with Request: \(error)")
}
if let jsonData = response.data{
let json = JSON(data: jsonData)
if let matched = json["match"].bool{
completion(matched.description)
print("Liked!: \(matched)")
if(sender <= SharedUsers.sharedUser.users.count){
self.like(sender: sender + 1, completion: {result in
//print(result)
})
}else{
return
}
if(matched){
}
}else{
"There is an error with JSON"
}}}
}else{return}
}}}

Related

Delay task until completed

It is a very common question for people to ask "How do I delay a function or a chunk of code?" but that is not what I need here.
I need my code to wait until a certain task is complete, otherwise my function receives an error that I have no access_token (as the code doesn't wait for fetching the data from the Spotify server).
Here is my code so far, with the attempt to add a DispatchGroup:
func getAccessToken() throws -> Spotify.JSONStandard {
var accessToken: Spotify.JSONStandard!
let group = DispatchGroup() // <- Create group
group.enter() // <- Enter group
Alamofire.request("https://accounts.spotify.com/api/token", method: .post, parameters: spotify.parameters, headers: nil).responseJSON(completionHandler: {
response in
// Check if response is valid
if let newValue = response.result.value as? Spotify.JSONStandard {
accessToken = newValue
}
group.leave() // <- Leave group
})
group.wait() // <- Wait until task is completed
// \/ Delay this code until complete \/
if accessToken != nil {
return accessToken
}
else {
throw SpotifyError.failedToGetAccessToken
}
// /\ /\
}
Without the groups, my code throws SpotifyError.failedToGetAccessToken (the access_token is nil).
However, after adding the groups, my code just hangs and waits forever. How can I delay this code from being completed?
I know getting the token has no issues, as if I remove the return and place a print within the request, I get my expected result.
If you have any questions, please ask
Don't try to make an asynchronous task synchronous
This is a solution with a completion handler and a custom enum for convenience
enum Result {
case success(Spotify.JSONStandard), failure(Error)
}
func getAccessToken(completion: #escaping (Result)->()) {
Alamofire.request("https://accounts.spotify.com/api/token", method: .post, parameters: spotify.parameters, headers: nil).responseJSON(completionHandler: {
response in
// Check if response is valid
if let newValue = response.result.value as? Spotify.JSONStandard {
completion(.success(newValue)
} else {
completion(.failure(SpotifyError.failedToGetAccessToken))
}
})
}
and call it
getAccessToken { result in
switch result {
case .success(let token) : // do something with the token
case .failure(let error) : // do something with the error
}
}

Semaphore to sync request Swift4 stopped forever

I need sync request from web service, so I use sempahore:
class func syncProducts() {
print("syncProducts() 1")
let idsLocal = getProductsIds()
let semaphore = DispatchSemaphore(value: 0)
var idsCloud : [Int] = []
print("Getting cloud ids... 1")
OdooService.getProductsIds { (params: [Int]) in
print("SuccessBlock size ids: \(params.count) 1")
idsCloud = params
semaphore.signal()
}
semaphore.wait()
print("Depois do GetproductsIds: 1")
}
but in this example, the app keep locks forever! The request never ends. This is my function to request data from webserver and returns to success block if is case.
static func getProductsIds(successBlock: #escaping (_ params: [Int]) -> Void) {
// check odoo auth
let dispatch = DispatchGroup()
if( OdooAuth.uid == 0 ) {
print("Odoo offline, tentando reconectar...")
dispatch.enter()
OdooAuth.reconnect(successBlock: { params in
print("Reconectado com sucesso...")
dispatch.leave()
}, failureBlock: { params in
print("Falha no ReAuth")
return
})
}
print("Start request from Odoo...")
let fieldsProducts = ["id"]
let options = [ "fields": fieldsProducts] as [String : Any]
var idsList : [Int] = []
let params = [OdooAuth.db, OdooAuth.uid, OdooAuth.password,"product.template","search_read",[],options] as [Any]
AlamofireXMLRPC.request(OdooAuth.host2, methodName: "execute_kw", parameters: params).responseXMLRPC {
(response: DataResponse<XMLRPCNode>) -> Void in
switch response.result {
case .success( _):
print("Success to get Ids")
let str = String(data: response.data!, encoding: String.Encoding.utf8) as String!
let options = AEXMLOptions()
let xmlDoc = try? AEXMLDocument(xml: (str?.data(using: .utf8))!,options: options)
//print(xmlDoc!.xml)
for child in (xmlDoc?.root["params"]["param"]["value"]["array"]["data"].children)! {
for childValue in child["struct"].children {
let id = childValue["value"]["int"].value!
idsList.append(Int(id)!)
//print("Id: \(id)")
}
}
successBlock(idsList)
break
case .failure(let error):
print("Error to get Ids: \(error.localizedDescription)")
break
} // fim switch
} // fim request
} // fim getProductsIds
I don't know if semaphores is the best way to do this, but I need sync the requests! I tried use DispatchGroup() like in reauth, but not works too.
I would expect that the deadlock is a result of getProductsIds callback being called on main thread, which is blocked by the semaphore. As far as I know, by default Alamofire dispatches the callback on the main thread, which I would expect is the case of AlamofireXMLRPC, since it is a wrapper around Alamofire.
I would strongly recommend to not block main thread during an async operation.
If, however, for any really really good reason you cannot do otherwise, you will need to make sure that the callback won't get dispatched on the main dispatch queue (since that one is blocked waiting for the signal). Alamofire itself has a response overload that allows to specify the DispatchQueue object on which to run the callback. It seems that AlamofireXMLRPC has one too, so I would try to utilize that and change
AlamofireXMLRPC.request(OdooAuth.host2, methodName: "execute_kw", parameters: params)
.responseXMLRPC {
// process result
}
to:
AlamofireXMLRPC.request(OdooAuth.host2, methodName: "execute_kw", parameters: params)
.responseXMLRPC(queue: DispatchQueue.global(qos: .background)) {
// process result
}
I based it on the github source code of AlamofireXMLRPC, but have not worked with it before, so maybe there will be some syntactic errors. But it should point you to right direction. Still, I would recommend you NOT to block the thread (I am repeating myself, but this is really very important point).

Synchronized a function call

Consider a scenario, I have a function "REFRESH TOKEN", this function is called by different methods simultaneously, let's say the methods are "A", "B", "C".
If method "A" calls "REFRESH TOKEN" firstly then methods "B" and "C" should wait until it finishes.
Does anybody have a sample swift code?
How can I attain this scenario? Appreciate your help!
let serialQueue = DispatchQueue(label: "serialQueue")
var myFlag = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.refresh(param: 1)
self.refresh(param: 2)
self.refresh(param: 3)
}
func refresh(param: NSInteger) -> Void {
let absolutePath = "MY SAMPLE API"
var headers: [String: String] = Dictionary<String, String>();
headers["Content-Type"] = "application/json"
serialQueue.sync {
print("\nEntered ", param)
Alamofire.request(absolutePath, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseString {
response in
switch response.result {
case .success:
print("SUCCESS")
break
case .failure(let error):
print(error)
}
}
}}
Output:
Entered 1
Entered 2
Entered 3
SUCCESS
SUCCESS
SUCCESS
I need an output like this:
Entered 1
SUCCESS
Entered 2
SUCCESS
Entered 3
SUCCESS
You can make use of DispatchGroup.
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
Method A {
//Refresh Token code here
dispatchGroup.leave()
}
dispatchGroup.wait()
dispatchGroup.enter()
Method B {
//Refresh Token code here
dispatchGroup.leave()
}
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
print("Both functions are invoked one after other")
}
The easy and nasty way is to set a flag and check the flag on calling the function and changing flag on returning.
A better way, as far as I found out, is using operation queue.
You should create a dispatchQueue(Serial) and put that code in sync of that queue.
//Create a dispatch Queue
var dispatchQueue:DispatchQueue
func refreshToken() {
dispatchQueue.sync {
//whatever code is there in refreshToken method
}
}
Request chaining is one way. If you find that you have to do this a lot then maybe look into futures and promises. Another way is to use Dispatch Group.
Request Chain example
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
Alamofire.request(.GET, "http://httpbin.org/get2\(data.something)", parameters: ["foo": "bar"]).response { (_, _, something, error) in
fulfill(something)
}
} else {
reject(error)
}
}
Dispatch Group Example:
DispatchQueue.global(qos: .userInitiated).async {
var storedError: NSError?
let downloadGroup = DispatchGroup()
downloadGroup.enter()
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo":
"bar"]).response { (_, _, data, error) in
downloadGroup.leave()
}
downloadGroup.wait()
downloadGroup.enter()
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo":
"bar"]).response { (_, _, data, error) in
downloadGroup.leave()
}
downloadGroup.wait()
downloadGroup.notify(queue: DispatchQueue.main) {
completion?(storedError)
}
}
If you want to look into Futures and Promises Alamofire made a lib
https://github.com/PromiseKit/Alamofire-

How do I prioritise my Alamofire requests so that they execute before the function returns?

So I have a function that returns a list of topics which is supposed to be returned via an api, but the response closure only executes after the functions returns.
My Code:
func getSectionsList (syllabus_ID: String) -> [String:String] {
var sectionDictionary = [String:String]()
var errorString: String!
let token = Keychain.value(forKey: "Auth_Token")! as String
manager.request(.GET, "\(BASE_URL)/syllabi/\(syllabus_ID)/sections?token=\(token)", encoding:.JSON).validate()
.responseJSON { response in
switch response.result {
case .Success:
let responseJSON = JSON(response.result.value!)
sectionDictionary = self.serializeJSON(responseJSON)
break
case .Failure(let error):
NSLog("Error result: \(error)")
errorString = "\(error)"
return
}
}
return sectionDictionary
}
I tried using completion handlers like this:
func getSectionsList (syllabus_ID: String, completionHandler: ([String:String], String?)->()) {
var sectionDictionary = [String:String]()
var errorString: String!
let token = Keychain.value(forKey: "Auth_Token")! as String
manager.request(.GET, "\(BASE_URL)/syllabi/\(syllabus_ID)/sections?token=\(token)", encoding:.JSON).validate()
.responseJSON { response in
switch response.result {
case .Success:
let responseJSON = JSON(response.result.value!)
sectionDictionary = self.serializeJSON(responseJSON)
break
case .Failure(let error):
NSLog("Error result: \(error)")
errorString = "\(error)"
return
}
completionHandler(sectionDictionary, errorString)
}
}
And called it like this:
var dictionary = [String:String]()
api.getSectionsList("1"){
(dict, error)in
if dict.count != 0{
dictionary = dict
}
}
So now it does return the value, but it feels like there must surely be a more simple way? Is it not possible to have a function that just returns the values I want without having to achieve it like I have done?
Nope, you've hit the nail on the head with the closure (completion handler) approach.
The Alamofire function .responseJSON() is coming back to you asynchronously which you also have to return asynchronously through the closure completion handler pattern.
Any returns inside a closure are returns for the closure itself, not the scope surrounding the closure.
You've defined your closure type as,
([String:String], String?)->()
AKA, to better illustrate my point, is the same as this,
[String:String], String?)->Void
That means your closure doesn't want a return value anyway (hence the Void) and I'd expect the compiler to complain if you tried to do so.
You'll get use to this async closure pattern the more you use it.
If you truly want a return value from your function, I think you might be able to do it with Alamofire using synchronous requests although I've never done it and I don't see a reason to lock up execution cycles why you wait for a relatively slow task to complete.
One other thing to be careful with is what thread your closure is originally getting called on. I believe .responseJSON() will always be returned on a background thread if I'm not mistaken. So, say you want to update your UI after that request returns, if its not on the main thread you will eventually run into issues. A quick test will tell you,
if NSThread.mainThread() == NSThread.currentThread() {
print("ON MAIN THREAD")
} else {
print("NOT ON MAIN THREAD")
}
To make sure its on the main do something like this with GCD,
dispatch_async(dispatch_get_main_queue(),{
completionHandler(sectionDictionary, errorString)
}

Async issue on getting values from the server

In my viewDidLoad I do:
// Get Profile Info
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
let getMyProfileURL = "\(self.property.host)\(self.property.getMyProfile)"
print(getMyProfileURL)
Alamofire.request(.POST, getMyProfileURL, parameters: self.property.profileParameteres, encoding: .JSON).responseJSON { response in
do {
let json = JSON(data: response.data!)
if json["user"].count > 0 {
self.profileDetails.append(ProfileDetailsModel(json: json["user"]))
}
}
}
print(self.profileDetails[0].firstName)
}
so, when I want to get the firstName from the array:
print(self.profileDetails[0].firstName)
my app crashes with the error:
fatal error: Array index out of range
My array is empty. I've used NSOperationQueue for it, but I still get empty array and crash. How can I fix this problem and access my filled array from anywhere in my controller, not only inside the Alamofire block?
The Request is Asynchronous, it will only fill the self.profileDetails array when it receive an answer, and execute the completionHandler block.
In your code, you call to execute the Asynchronous task, it will run in another thread, and the app goes back to the next operation and immediately try to access the self.profileDetails, with at the time may still be nil.
You must wait the completion handler block finish to fill the array, and then access the data.
The code of #Md.Muzahidul Islam , will work as you want.
Now it will work fine
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
let getMyProfileURL = "\(self.property.host)\(self.property.getMyProfile)"
print(getMyProfileURL)
Alamofire.request(.POST, getMyProfileURL, parameters: self.property.profileParameteres, encoding: .JSON).responseJSON { response in
do {
let json = JSON(data: response.data!)
if json["user"].count > 0 {
self.profileDetails.append(ProfileDetailsModel(json: json["user"]))
}
}
print(self.profileDetails[0].firstName)
}
}
I'd suggest refactoring your code to move this to a function outside of your viewDidLoad and then adding your own completion handler. Something like:
func getData(completion: () -> Void) {
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
let getMyProfileURL = "\(self.property.host)\(self.property.getMyProfile)"
print(getMyProfileURL)
Alamofire.request(.POST, getMyProfileURL, parameters: self.property.profileParameteres, encoding: .JSON).responseJSON { response in
do {
let json = JSON(data: response.data!)
if json["user"].count > 0 {
self.profileDetails.append(ProfileDetailsModel(json: json["user"]))
}
}
completion()
}
}
}
Then in viewDidLoad you can call it with its completion handler to access the data outside of the Alamofire block after it's finished, e.g.
getData() {
print(self.profileDetails[0].firstName)
}

Resources