Semaphore to sync request Swift4 stopped forever - ios

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).

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

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-

Synchronous Alamofire Request with DispatchGroup

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

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

Alamofire completionHandler of responseJSON is not called

I have the following code to fetch the replies to a list of comments. (1 comment has many replies)
static func fetchCommentsAndTheirReplies(articleId: String, failure: (()->Void)?, success: (comments: [[String: AnyObject]], replies: [[[String: AnyObject]]], userIds: Set<String>)->Void) {
var retComments = [[String: AnyObject]]()
var retReplies = [[[String: AnyObject]]]()
var retUserIds = Set<String>()
Alamofire.request(.GET, API.listComment, parameters: [API.articleId: articleId]).responseJSON {
response in
guard let comments = response.result.value as? [[String: AnyObject]] else {
failure?()
return
}
print(comments)
retComments = comments
let group = dispatch_group_create()
for (commentIndex, comment) in comments.enumerate() {
guard let id = comment["_id"] as? String else {continue}
let relevantUserIds = parseRelaventUserIdsFromEntity(comment)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
retReplies.append([[String: AnyObject]]())
dispatch_group_enter(group)
Alamofire.request(.GET, API.listReply, parameters: [API.commentId: id]).responseJSON {
response in
if let replies = response.result.value as? [[String: AnyObject]] {
for (_, reply) in replies.enumerate() {
let relevantUserIds = parseRelaventUserIdsFromEntity(reply)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
}
//TODO: need to capture commentIndex?
retReplies[commentIndex] = replies
}
dispatch_group_leave(group)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
success(comments: retComments, replies: retReplies, userIds: retUserIds)
}
}
The complete handler of API.listReply request is never called. dispatch_group_enter(group) is called once, and dispatch_group_leave(group) is never called. The code gets stuck at dispatch_group_wait.
What's strange is, even the UI is stuck, which is strange because the entire function is async.
I have encounter similar problem:
on Main UI thread, call:
dispatch_semaphore_wait(loginDoneSemaphore, DISPATCH_TIME_FOREVER)
and call http using Alamofire, internal also use your httpRequest.responseJSON
-> finally found code inside responseJSON completion handler never called
-> cause find code after DISPATCH_TIME_FOREVER never called
-> finally find root cause is: Alamofire's responseJSON, default, if you not pass in the thread/queue, will run on Main UI thread
-> before call Alamofire, have inside Main UI thread, to use DISPATCH_TIME_FOREVER lock UI thread
-> so following Alamofire's responseJSON, which run also on Main UI thread, will never called
-> my solution is: designate Alamofire do http response on another thread:
let BackgroundThread:dispatch_queue_t = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
func dispatchBackground_async(thingsTodo:()->()) {
dispatch_async(BackgroundThread, thingsTodo)
}
dispatchBackground_async({
httpRequest.responseJSON(queue: BackgroundThread, completionHandler: { response in
gLog.debug("request=\(response.request), response=\(response.response), statusCode=\(response.response?.statusCode), result=\(response.result)")
// [Debug] [com.apple.root.background-qos] [CrifanLibHttp.swift:21]
})
this maybe useful for you to refer.
-> maybe you can use my method: set another thread for Alamofire's responseJSON, to solve your problem.
I'm not familiar with Alamofire internals, but it seems likely that it fulfills all your response completionBlocks on the same serial queue. Your dispatch_group_wait is blocking other responses from finishing and calling their dispatch_group_leaves.
You can solve this problem by using dispatch_group_notify instead of dispatch_group_wait, so rather than blocking the thread it will simply submit the block to a queue when necessary.

Resources