Swift completion handler for network request causing SIGABRT - swift

I'm using a completion handler to preform a network request, Once the network request is completed I add the result to a dictionary i've constructed that contains other information pertaining to the result. Then I call a function to change the text of the labels in my view controller. The code I'm using is:
requestCalculateTotals(data["id"] as! String, pickupDate: data["pickup_date"] as! String, dropoffDate: data["dropoff_date"] as! String){
(calculateData: NSDictionary) in
self.data.addEntriesFromDictionary(calculateData as [NSObject : AnyObject])
self.refreshDetails()
}
This causes a SIGABRT error and I dont understand why? I've tested the completion handler and the network request and they both work, I can print the result data and it contains the data I expect. The refreshDetails function works outside of the completion handler but then it won't be waiting for the network request.
Is there something I've missed when constructing the completion handler or is there a there a better way I could wait for a network request?
EDIT I get the error: "This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release." How could I add my refreshDetails function in the main thread when the network request is complete?

Related

Got timeout error -1001 after some HTTP requests using Swift

I create an http request using PUT to get data from the server. I got this data from the server and transform it on a PDF file.
When I run it for the first time everything runs fine, but after some calls, I start to get timeout errors.
Sometimes, I need to restart the app to be able to receive HTTP requests again.
This is the code that I use.
func callGetPdfFromEndpointUsingNSMutableURLRequest() {
if codigoBarra == "" {
messageError = "Código não localizado"
showingAlert = true
redirectToPdfView = false
showingActivityIndicator = false
return
}
let serviceRepository = ServiceRepository()
// let codigo_barra = "d152d36914313fedfbf36842a7195b723"
let json: [String: Any] = ["codigoBarra":"\(codigoBarra)"]
let request: NSMutableURLRequest = serviceRepository.clientURLRequest(endpointPesquisa, typeAutho: .basic, parms: "", body: json as Dictionary<String, AnyObject>)
print("request: \(request)")
print("request.url: \(String(describing: request.url))")
serviceRepository.put(request, retryLogin: true, completion: {isOk,msgError,httpCode,needLogin, response in
if isOk {
tratarRequisicaoPdf(response)
} else {
print("erro no request - is not ok | - httpCode: \(httpCode)")
var stringResponse:String = ""
if response != nil {
stringResponse = String(data: response as! Data, encoding: .utf8)!
} else {
stringResponse = "Sem resposta do servidor, tempo limite da solicitação foi esgotado."
}
messageError = "\(stringResponse)"
print(messageError)
showingAlert = true
redirectToPdfView = false
semaphore.signal()
}
semaphore.wait()
showingActivityIndicator = false
})
}
This error is unstable, sometimes it shows, sometimes it don't appear.
The people working on backend was not able to detect any problems.
I got the following error:
2022-05-20 15:33:15.442419-0300 CDA[2016:38068] Task <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1> finished with error [-1001] Error Domain=NSURLErrorDomain Code=-1001 "Esgotou-se o tempo limite da solicitação." UserInfo={_kCFStreamErrorCodeKey=-2102, NSUnderlyingError=0x7fd0e5245520 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>"
), NSLocalizedDescription=Esgotou-se o tempo limite da solicitação., NSErrorFailingURLStringKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, NSErrorFailingURLKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, _kCFStreamErrorDomainKey=4}
response::: nil
erro: response is nil
httpCode: 0
What can I do to try to detect what was causing the timeout errors?
Edit:
I added the source code that can be viewed here https://swiftfiddle.com/mz2dxw6z6bda7a6t44cryncrpi.
NEW EDIT:
Answers to comments created by #Rob
Good to know about the NSUrlMutableRequest
I will try to use the 'finishAndInvalidate' in my URLSession. I didn't know about that.
My problem is unpredictable. Now I start the app and the first call got an timeout, after the second call the app works. Sometimes it starts working, but after some requests, I got a timeout
In the absence of a MCVE, there is not enough here to diagnose or reproduce the problem.
That having been said, there are some interesting clues:
You are calling wait (on a semaphore). Eliminate the DispatchSemaphore and if you want to know when the requests are done, use DispatchGroup. But when you use the dispatch group, use notify when it is done, not wait. Avoid blocking threads unnecessarily. And never block the main thread.
Your network request is performed by ServiceRepository, which you have not shared with us. But in your fiddle link, you show us unrelated URLSession code that is specifying the main queue as its delegate queue. If ServiceRepository is doing something similar, that, combined with the wait, above, could easily deadlock your code.
So, eliminate the semaphore, avoid ever calling wait (whether semaphore or dispatch group), and the deadlock risk is eliminated.
That having been said, that is only one potential timeout risk. The other scenario might be that you are simply issuing too many requests for the URLSession to run them without timing out.
If that is the case, you have a few options:
Increase the timeout threshold (either of the request or the session).
Try bumping up the timeout values and see if that mitigates the issue.
Submit uploads in just-in-time manner.
E.g., you might have a process where, rather than initiating all the uploads at once, that you issue each upload upon the completion of the prior upload. Or you can wrap the upload in a custom, asynchronous, Operation subclass (which is complicated in its own right), and then tell the operation queue to only attempt 4 at a time, or whatever. There are many techniques to tackle this, but the idea is to prevent timeouts by not even submitting the upload until you are confident that the URLSession can perform them.
Use background URLSession with file-based uploads.
If you have uploads that are so slow that they risk timing out, it begs the question of whether you really want to force the user to keep the app running in order to let the uploads finish at all. If you use a background URLSession, the background daemon will not let them timeout, but rather will upload them when it can.
Refactoring this for background URLSession is a non-trivial exercise (the first time you do it, anyway). You have to abandon completion handlers, use delegate-methods API, use file-based uploads, etc. But it is an incredibly powerful way to let uploads proceed in the background, allowing the user to leave the app, etc.
I don't see full code but you should have a look into semaphore usage - probably wrong multithread logic leads to not complete you request and your completion is hovered for a long period and causes URLDataTask to produce the timeout error

How to call custom method right before sessionTask resume in Alamofire

I need to record some data/info when Alamofire called resume to start the request.
(I used Swift in my project)
Is there anyway without do method_swizzling?
the timeline will be like this:
Call a request (put in request queue or execute right away) -> [custom method] -> SessionTask.resume()
I know Moya did something similar called WillSend. But I would like to know how to do it without using Moya.
Thank you.
If all you need to do is inspect various request elements you can use Alamofire 5's EventMonitor protocol, which is called during various lifetime events as Alamofire makes requests. There's a built in ClosureEventMonitor which allows you to set closures for those events. For example:
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Please see our documentation for ClosureEventMonitor and the EventMonitor protocol itself.

Reactive asynchronous feedback system with RxSwift

I am designing a call manager with the help of RXSwift (ReactiveX) that continuously interacts with an API. The call manager comprises several objects that itself comprises an indicator (indicating status information loaded from the API) and control (requests to be sent to the API).
class CallManagerObjectA() {
var control = PublishSubject<String>()
var indicator = BehaviorSubject<String>(value: "string status")
}
Within the call manager, a scheduler regularly provides new values to the indicator observable:
<... API response ...>
indicator.onNext(newValue)
Somewhere else in a view controller, the indicator will be observed for a label:
indicator.subscribe(onNext: { label.stringValue = $0 })
Within the same view controller, the user can control the object status via GUI elements continuously:
control.onNext(commandValue)
Within the call manager, the control will be observed for an API call:
control.subscribe(onNext: { (command) in
// API request call
})
So far so good, this is working very well with reactive patterns.
Now, I am looking for a good solution to handle errors, if the call manager recognizes errors during the API interaction and show these errors to the user in the view controller. I was immediately thinking of something like this:
// Call manager recognizes the error
control.onError(error)
...
// Call manager ignores errors for the subscriber
control.retry().ignoreErrors().subscribe(onNext: { (command) in
// API request call
})
...
// View controller shows the errors
indicator.subscribe(onNext: { label.stringValue = $0 })
control.subscribe(onError: { print("error", $0) })
This however ends up in an infinite loop.
I fear that I have a fundamental understanding issue with reactive programming, or I miss something very important, but I am not able to understand how the handle errors in this reactive pattern environment.
Based on the code you have shown, you have a big misunderstanding, not just with how to handle Errors, but with how to program reactively in general. Try watching this video "Reactive Programming: Why It Matters"
To answer your specific question, there are two misunderstandings here:
When you call control.onError(_:) it will be the last call you will be able to make on control. Once it emits an error it will stop working.
The retry() operator asks its source to "try again on Error". If it's source is determinate, then it will just do the exact same thing it did before and emit the exact same output (i.e., the same error it emitted last time.) In the case of a PublishSubject, it doesn't know why onError was called. So the best it can do is just emit the error again.
Honestly, I consider this a bug in the API because subscribing to a publish subject that emitted an error at some point in the past should just do nothing. But then, you wouldn't be asking why you got an infinite loop. Instead you would be asking why your control stopped emitting events.

Semaphore in Swift not working for Firebase Authentication

I am trying to achieve functionality similar to Javascript/C#'s async/await. I am trying out the use of semaphores, and found that it works with URLSession in my XCode Playground.
Thus, i am now trying to perform the same thing with Firebase Authentication using the following code:
var response:String? = "test"
let semaphore = DispatchSemaphore(value: 0)
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in
if err != nil {
response = "\(String(describing: err))"
}else{
response = nil
}
semaphore.signal()
}
let _ = semaphore.wait()
if response == nil{
self.transitionToHome()
}
However, the simulator freezes forever, appearing as if the semaphore.signal() never got called. Placing print statements near the semaphore.signal() didn't appear as well. I've also placed the firestore code in a DispatchQueue.global(qos: .background).async and subsequently tried to retrieve the response value in DispatchQueue.main.async but the response did not get updated as well. The code below reflects what i did:
DispatchQueue.global(qos: .background).async {
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in
if err != nil {
response = "\(String(describing: err))"
}else{
response = nil
}
semaphore.signal()
}
let _ = semaphore.wait()
}
if response == nil{
self.transitionToHome()
}
While this did not freeze up the UI, the response value was not picked up after the DispatchQueue was called. I've also called the if-else block within a DispatchQueue.main.async block but that had the same result too.
Further, after waiting for a period of time, i see this error popping up in my xcode terminal:
020-01-02 01:33:25.447842+0800 das-carddeckapp[78136:10508853] Connection 4: received failure notification
2020-01-02 01:33:25.448179+0800 das-carddeckapp[78136:10508853] Connection 4: failed to connect 1:50, reason -1
2020-01-02 01:33:25.448387+0800 das-carddeckapp[78136:10508853] Connection 4: encountered error(1:50)
2020-01-02 01:33:25.457587+0800 das-carddeckapp[78136:10508853] Task <3A3720D6-7549-4C31-96A2-C88B89294821>.<1> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
I know that using completion handlers will make this work, but I want to try to get this to work before resorting to completion handlers. Any help is greatly appreciated!
The first example (where you’re calling wait on the main thread) is “deadlocking” because it is blocking the main thread with wait, which is to be signaled from the createUser closure, which also wants to run on the main thread (which you’ve blocked).
I know that semaphores is considered a bad pattern but i just wanted to understand why is it not working.
Your second example, where you are dispatching the wait to the global queue resolves the deadlock. But because you’ve dispatched that asynchronously, that means that the main thread will carry on as the global queue waits for the response. As a result, you are almost guaranteed to not have a value ready to return. (And if you attempt to change this to call the global queue synchronously, you’d just re-introduce the deadlock.)
In short, you can’t easily have the main queue wait for an asynchronous method which calls its completion handler on the main queue. That is a programmatic “Catch-22”.
But setting that aside, if this is an iOS app, it just simply a serious problem to ever block the main thread (even if you could solve the deadlock). It’s a horrible UX when an app freezes. This is especially true for unfortunate mobile users who find themselves on poor cellular connection, where it might result in a non-trivial delay. Worse, if you’re unlucky enough to block the main thread at the wrong time, the iOS watchdog process may unceremoniously kill your app.
Hey, I get it. We’ve all been there. When we first encountered asynchronous programming patterns, like network requests, it feels it would be so much more logical and intuitive if we could just wait for the response. But it should be avoided at all costs. You’ll be much happier in the long run if you stick with well-established asynchronous programming patterns, rather than fighting it.

cancel request when its already finished

consider the following (pseudo) code:
let request = Alamofire.request(...) {
//handler called when requests has been completed
//do some processing here
}
//some processing here, could take a while
request.cancel()
Question:
what happens if the request has already been fully completed (and the handler called) when the request.cancel() is done?
Will this return an error?
Is the handler called again?
Or (what I am hoping for) nothing...?
If the handler called that means request has its answer that can mean two things: Either request is succesfull, you have what you asked or request is not succesfull which means you will get an error.
Either way if you got your response request.cancel() will mean nothing.