Alamofire completionHandler of responseJSON is not called - ios

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.

Related

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

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 array from a GET request function in viewdidload function in swift

I'm very new to swift, so I will probably have a lot of faults in my code but what I'm trying to achieve is send a GET request to a server with paramters inside a function. I want to use the array I receive from the server in my viewdidload and in other functions but cant seem to find a way to store the array so i can use it. in my function it is filled, but out of my function it is empty
var scenarioArray: Array<Any> = []
let idPersoon = UserDefaults.standard.object(forKey: "idPersoon") as! String
override func viewDidLoad() {
super.viewDidLoad()
ScenarioArray()
print(scenarioArray)
print(self.scenarioArray)
}
func ScenarioArray() {
var request = URLRequest(url: URL(string: "http://dtsl.ehb.be/app&web/ios_php/getAllScenariosByPersoon.php?persoonID="+idPersoon)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
}
Your "problem" is that you are trying to GET data from a server, meaning that you are doing a network call.
Now...you don't know how long that network call will take when you launch it, if you are on a good network then it might be fast, but if you are on 3G network it might take a while.
If the call to your server was done synchronously, the result would be that each and every time you'd try to fetch data your code would focus on doing just that, meaning that nothing else would go on... that is not what you want :)
Instead, when you use URLSession, and call task.resume() that method is executed asynchronously, meaning that it starts on another thread in the background where it will fetch data.
In the meantime, your main thread is free to handle UI rendering and so on. At some point in the near future your network call finishes and you now have valid data and must inform whoever needs to know.
So when you do a call to dataTask(with: completionHandler:), what you are actually saying is something along the lines of:
"hey...go fetch this data in the background please, and when you're done, I'd like to execute the code I've passed you here in the completionHandler with the parameters you tell me about".
Hope that makes just a little sense :)
Now...you have this code:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
That last part of the function call ({ data, response, error in...) is the completionHandler, which is not executed straight away. It is not executed until the retrieval of data has completed.
And therefore when you do a call to your ScenarioArray() function in viewDidLoad, what will happen is that the asynchronous call to fetch data will start in the background and your viewDidLoad will continue what it is doing, meaning that when you say:
print(scenarioArray)
print(self.scenarioArray)
then you can not expect scenarioArray to be populated yet as your task is busy fetching that data in the background.
So...what you need to do, as #vadian says, is to update your UI once the data has been fetched, meaning, in the completionHandler.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
//Now you have data, reload the UI with the right scenarioArray
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
Hope that makes sense and helps you.

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