How to handle parse JSON errors in Swift [closed] - swift

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm new in Swift and though I've read responses to many questions with a similar title, since there are apparently so many ways to handle HTTP calls in Swift, I'm completely confused.
I use URLSession.shared.dataTask to make HTTP calls to an open API and handle the response as
let fiatRates = try! JSONDecoder().decode(FiatResponse.self, from: data!)
DispatchQueue.main.async {
completion(fiatRates)
}
where FiatResponse is
struct FiatResponse: Decodable {
var rates: [String: Float]
var base: String
var date: String
}
When I wrap this is do-catch, I get this warning:
Catch block is unreachable because no errors are thrown in do block
Yet the app crashes with an error in the do block in case the API returns errors or times out. In. such situation the app never runs the catch block.
How can I tweak my code to correctly parse the JSON response and run the catch block, should an error occur?

When you add ! to try the error will not be thrown, instead it will crash the app. To make the do-catch work you need to get rid of the !:
do {
let fiatRates = try JSONDecoder().decode(FiatResponse.self, from: data!)
} catch {
print(error)
}
You can read more about it here, in this blogpost. Also, here is the full documentation.

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

Unable to return value via Platform Channel when using Swift Callback Handlers

I am working with Flutter and Swift. I'm trying to authorize a credit card using the AuthrizeNet SDK, which unfortunately, does not natively support Flutter. To get around this, I created a Platform Channel in my iOS AppDelegate, which I successfully managed receive data in from Flutter. My question now is how can I return that data from inside the callback handler of the authorizing function (from the AuthorizeNet SDK)? When trying to call the Flutter result function, the Swift compiler throws this error: Escaping closure captures non-escaping parameter 'result'. Here's my code:
handler!.getTokenWithRequest(request, successHandler: { (inResponse:AcceptSDKTokenResponse) -> () in
let paymentResponseModel: PaymentResponseModel = PaymentResponseModel.init(
token: inResponse.getOpaqueData().getDataValue(),
resultCode: inResponse.getMessages().getResultCode(),
tokenResultDescription: inResponse.getOpaqueData().getDataDescriptor(),
messageCode: inResponse.getMessages().getMessages()[0].getCode(),
messageText: inResponse.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}) { (inError:AcceptSDKErrorResponse) in
let paymentResponseModel: PaymentModelErrorResponse = PaymentModelErrorResponse.init(
code: inError.getMessages().getResultCode(),
message: inError.getMessages().getMessages()[0].getCode(),
details: inError.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}
I've tried different way of calling the result function outside of the getTokenWithRequest function, but I couldn't get it to work. I'm fairly new to Swift programming, so I apologize if this question is vague or has a simple solution. Thank you!
The problem is that the successHandler will be called in the future (as a callback), but result must be called immediately and you must return from the method of your code snippet immediately. So, how do you get the success/error response back? You have to make a call from native to Dart when the the token response becomes available. Then, at the Dart end, you'll have to hang about until that response call is made when you can update your app state, and the UI.
It becomes a bi-directional method channel like this:
---token request--->
<---result----------
<---token response--
----result--------->
Neither of the results contains much information, but could be used to convey errors. For example, if the first result comes back false, something went wrong and the Dart end shouldn't expect the token response - it won't happen.

uncaught exception of type NSException with AlamoFireImage when url isn't an image

I am using AlamofireImage to display images in a UITableViewCell
typealias boolAndAnyCompletion = (_ success: Bool, _ value: Any?) -> Void
class ImageHelper {
func fetchImage(url:String, completion: #escaping boolAndAnyCompletion) {
Alamofire.request(url).responseImage { response in
if let image = response.result.value {
completion(true, image)
} else {
completion(false, "No image")
}
}
}
}
This is mostly working fine. I am taking a url from a JSON object and attempted to fetch image at the url. Mostly this works fine and either returns image as expected or fails if the url string is 404 or otherwise invalid.
However today I started getting my app crashing with
libc++abi.dylib: terminating with uncaught exception of type NSException
I narrowed this down to the above method where my JSON response was giving me a url in error that was not pointing to an image.
if if the url I got for an image was "https://bbc.co.uk/news/" that that causes the crash. However if I search for an image at "https://www.google.co.uk/maps/" that fails as expected without crashed and dealt with by error handling.
I know that the best solution is for only correct image urls to be put in JSON but were dealing with humans doing that and mistakes may happen. So, Is there a reason one would 'fail correctly' which the other crashed my app? How can I prevent this crash on some invalid urls?
I found the solution to my problem and not surprised I got no answer as the problem wasn't above. I look at previous answer here and set an exception break point.
My app is also using Realm (which didn't seem relevant at first). I was populating UITableViewCell with data from Realm. Then I downloaded JSON and created new objects and deleted old realm ones.
The exception break point then stopped on
#throw RLMException(#"Object has been deleted or invalidated.")
In the cocoapod. As I was saving a string or the url and not the url in realm I'm guessing the cell started download, was then invalidated and assuming thats what caused the crash.

Swift completion handler for network request causing SIGABRT

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?

Empty response on long running query SailsJS

I'm currently running SailsJS on a Raspberry Pi and all is working well however when I execute a sails.models.nameofmodel.count() when I attempt to respond with the result I end up getting a empty response.
getListCount: function(req,res)
{
var mainsource = req.param("source");
if(mainsource)
{
sails.models.gatherer.find({source: mainsource}).exec(
function(error, found)
{
if(error)
{
return res.serverError("Error in call");
}
else
{
sails.log("Number found "+found.length);
return res.ok({count: found.length});
}
}
);
}
else
{
return res.ok("Error in parameter");
}
},
I am able to see in the logs the number that was found (73689). However when responding I still get an empty response. I am using the default stock ok.js file, however I did stick in additional logging to try to debug and make sure it is going through the correct paths. I was able to confirm that the ok.js was going through this path
if (req.wantsJSON) {
return res.jsonx(data);
}
I also tried adding .populate() to the call before the .exec(), res.status(200) before I sent out a res.send() instead of res.ok(). I've also updated Sails to 11.5 and still getting the same empty response. I've also used a sails.models.gatherer.count() call with the same result.
You can try to add some logging to the beginning of your method to capture the value of mainsource. I do not believe you need to use an explicit return for any response object calls.
If all looks normal there, try to eliminate the model's find method and just evaluate the request parameter and return a simple response:
getListCount: function(req, res) {
var mainsource = req.param("source");
sails.log("Value of mainsource:" + mainsource);
if (mainsource) {
res.send("Hello!");
} else {
res.badRequest("Sorry, missing source.");
}
}
If that does not work, then your model data may not actually be matching on the criteria that you are providing and the problem may lie there; in which case, your response would be null. You mentioned that you do see the resulting count of the query within the log statement. If the res.badRequest is also null, then you may have a problem with the version of express that is installed within sailsjs. You mention that you have 11.5 of sailsjs. I will assume you mean 0.11.5.
This is what is found in package.json of 0.11.5
"express": "^3.21.0",
Check for any possible bugs within the GitHub issues for sailsjs regarding express and response object handling and the above version of express.
It may be worthwhile to perform a clean install using the latest sailsjs version (0.12.0) and see if that fixes your issue.
Another issue may be in how you are handling the response. In this case .exec should execute the query immediately (i.e. a synchronous call) and return the response when complete. So there should be no asynchronous processing there.
If you can show the code that is consuming the response, that would be helpful. I am assuming that there is a view that is showing the response via AJAX or some kind of form POST that is being performed. If that is where you are seeing the null response, then perhaps the problem lies in the view layer rather than the controller/model.
If you are experiencing a true timeout error via HTTP even though your query returns with a result just in time, then you may need to consider using async processing with sailjs. Take a look at this post on using a Promise instead.