I have this code:
let goAheadAction = UIAlertAction(title: "Go Ahead", style: .default) { _ in
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
Server.POSTRequest(accessPoint: "/UserServices/forgotPassword", params:["emailAddress" : self.emailAddressTextField.text!], handler: {
(data: Data?, response: URLResponse?, error: Error?) -> Void in
// TODO: Only considered successful case
/*let confirmAlertController = UIAlertController(title: "Email been sent", message: "Please check your mailbox", preferredStyle: UIAlertControllerStyle.alert)
self.present(confirmAlertController, animated: true, completion: nil)*/
dispatchGroup.leave()
})
let waitResult = dispatchGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
if waitResult == .success {
let emailSentAlertController = UIAlertController(title: "Email Sent", message: "We've sent an password reset link to \"\(self.emailAddressTextField.text!)\"", preferredStyle: UIAlertControllerStyle.alert)
let gotItAction = UIAlertAction(title: "Got it", style: UIAlertActionStyle.cancel, handler: nil)
emailSentAlertController.addAction(gotItAction)
self.present(emailSentAlertController, animated: true, completion: nil)
} else {
let timeOutAlertController = UIAlertController(title: "Time Out", message: "Failed to request service, reason: \"Time Out\"", preferredStyle: UIAlertControllerStyle.alert)
let gotItAction = UIAlertAction(title: "Got it", style: UIAlertActionStyle.cancel, handler: nil)
timeOutAlertController.addAction(gotItAction)
self.present(timeOutAlertController, animated: true, completion: nil)
}
}
Every time I let dispatchGroup to wait, it always return timeout. What's wrong? I set it to 10000000000 nano sec, which should be 10 sec right? And my request did certainly get back before 10 seconds.
In order to do this: Try logging in again to see if the problem is resolved If Content Filtering is enabled, it may cause a time-out error. To change this setting:
If Content Filtering is enabled, it may cause a time-out error. To change this setting: These first two settings should fix the issue, but if you find the errors continue, try the following:
You attach multiple work items to a group and schedule them for asynchronous execution on the same queue or different queues. When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing. Creates a new group to which you can assign block objects.
When you encounter the “ping request timed out” error, you should change the IP address in your ping command and check whether it works properly. You can try ping another website or the localhost.
A couple of thoughts:
As rmaddy said, if you want to wait 10 seconds, the correct syntax would be:
let waitResult = dispatchGroup.wait(timeout: .now() + 10)
Your syntax is not going to wait at all, and it's going to look like it instantly timed out.
You shouldn't be waiting at all.
First, you should never block the main thread. At worst, you could have your app killed by the watchdog process if you do this at the wrong time. At best, it's a horrible UX (i.e. the app will appear to be completely frozen to the end user).
Second, depending upon how POSTRequest
was implemented, you might introduce a new problem. Specifically, if POSTRequest
calls its completion handler on the main queue (e.g. the default behavior for network libraries like Alamofire), you could deadlock until with wait
timeout on the main thread expires. If you are going to wait (which you shouldn't do anyway), you want to make sure you never wait on the same queue that your completion handler will use.
Note, if the completion handler isn't running on the same queue that you are waiting, there is no deadlock risk, but you still suffer from the problem I mentioned earlier, namely that you're blocking the main thread, which should always be avoided.
You should instead move all that code inside the closure. And if that closure is running on a background thread, make sure to DispatchQueue.main.async { ... }
to dispatch it back to the main queue.
So, the correct pattern is something like the following.
If you want your request to timeout after 10 seconds, build that into the original request. Note the timeoutInterval
:
class func POSTRequest(accessPoint: String, params: [String: String]?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
let url = URL(string: baseURLString)!
.appendingPathComponent(accessPoint)
let request = URLRequest(url: url, timeoutInterval: 10)
URLSession.shared.dataTask(with: request, completionHandler: completionHandler)
}
Clearly, you may be doing something else in POSTRequest
, so don't get lost in the details there. The key point is to build the timeoutInterval
into the request.
Then, make sure your UIAlertAction
starts the request and simply puts all of the processing after the POSTRequest
inside its completionHandler
, thereby eliminating the need for any dispatch group waiting or anything like that:
let goAheadAction = UIAlertAction(title: "Go Ahead", style: .default) { _ in
let email = self.emailAddressTextField.text!
let parameters = ["emailAddress": email]
Server.POSTRequest(accessPoint: "/UserServices/forgotPassword", params: parameters) { data, response, error in
DispatchQueue.main.async {
guard error == nil else {
let alert = UIAlertController(title: "Time Out", message: "Failed to request service, reason: \"Time Out\"", preferredStyle: .alert)
alert.addAction( UIAlertAction(title: "Got it", style: .cancel))
self.present(alert, animated: true)
return
}
let alert = UIAlertController(title: "Email Sent", message: "We've sent an password reset link to \"\(email)\"", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Got it", style: .cancel))
self.present(alert, animated: true)
}
}
}
This way, no threads are blocked.
Your issue appears to be with your use of DispatchTime(uptimeNanoseconds:)
.
From its documentation:
Creates a time relative to the system clock that ticks since boot.
You want a time relative to "now", not when the system clock was last booted.
DispatchTime
provides now
for this purpose.
Change the line:
let waitResult = dispatchGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
to:
let waitResult = dispatchGroup.wait(timeout: DispatchTime.now() + 10)
This will wait 10 seconds from "now".
This can be shorted to:
let waitResult = dispatchGroup.wait(timeout: .now() + 10)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With