Swift 5.5: Async/Await URLSession.shared.data() throws an error - ios

I tried to make use of the new Async/Await features in Swift 5.5 and tried the following code
let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(id)&country=at")
let (data, _) = try await URLSession.shared.data(from: url!)
let resultStruct = try jsonDecoder.decode(ResponseStruct.self, from: data)
Every time I execute this, the try await URLSession.shared.data(from: url!) part throws an error. If I catch it and print error.localizedString, I always get "cancelled". This happens with all different kinds of URLs. I tried to stick to the tutorials I found online, but what am I missing here?
EDIT: I forced the app into a runtime exception to get more details of the error:
Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSURLErrorDomain Code=-999 "cancelled"
As this post explains NSURLErrorDomain error code -999 in iOS, this error occurs when the SSL certificate of the server has issues, which I don't think is the case, as I am accessing the iTunes server or when the request gets canceled by anything else in my app, which looks like to be the case for me.

Change http to https. You cannot normally call insecure calls unless you add an exception to your Info.plist to disable App Transport Security.
There should be also a log from App Transport Security in your console.
Since ATS blocks the connection, the request gets cancelled.
See NSAppTransportSecurity for more info

I encountered the same problem in a SwiftUI app. I was using the task view modifier to load data asynchronously and update the view based on the loading states.
As mentioned in the documentation:
If the task doesn’t finish before SwiftUI removes the view or the view changes identity, SwiftUI cancels the task.
If this is your case, make sure to update the UI only after the task completes.
Alternative solution
Alternatively you can use the onAppear view modifier (which doesn't cancel the Task) and run the task inside its body:
Color.clear.onAppear {
Task {
await viewModel.load()
}
}

Related

nw_read_request_report [C9] Receive failed with error "Software caused connection abort"

I got this error with application connection lost. While redirecting from another app to my app I face this issue.This issue triggered only on live app, getting error with connection lost and while debugging with Xcode getting error but redirected to specific view controller successfully I used deep linking with url scheme for handling response from another app. Still not getting clarity to what exact issue is there because not able to debug live app issue.
Working on iOS 13.2
In AppDelegate:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true)
let params = components?.queryItems
signedResponse = (params?.first(where:{$0.name == "signedResponse"})?.value)!
self.decodedMsgString = String(data:Data(base64Encoded: signedResponse)!,encoding:.utf8)!
print("decodedMsgString : \(decodedMsgString)")
//Call API here
return true
}
I also ran into this issue, maybe this could give you an insight?
https://forums.developer.apple.com/thread/106838
From one of the replies
Following up, we determined that the issue was caused because our app continued to issue new NSURLConnection requests after going into the background and wasn't explicitly making them background tasks. As we didn't need background syncing, putting in code to prevent new requests from going out once the app was in the background eliminated this error.
There was the same problem, after returning to the application, it was necessary to make a short pause before requesting data from the deep link.
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
YourNetworkResponse
}

iOS 12 URLSession breaks when HTTP request hits and suddenly app enters in background

For all iOS Version < 12, it is working fine.
I'm testing(iOS ~> 12.x) in my app using basic URLSession and also tried with Alamofire.
Test Steps:
1. Hit Any HTTP/API Call.
2. Tap on home button immediately.
On coming after sometime it comes
2018-12-14 13:43:46.968901+0530 NewReader[15364:4847228] Task <519A3F27-90DA-439F-8711-B07EFA62E823>.<1> load failed with error Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <519A3F27-90DA-439F-8711-B07EFA62E823>.<1>, _kCFStreamErrorDomainKey=1, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <519A3F27-90DA-439F-8711-B07EFA62E823>.<1>"
), _kCFStreamErrorCodeKey=53} [53]
I don't know how other app manages this issue.
Also raised issue here
Let me know fixes or workaround.
P.S.: I already tried with Dispatch or Delay it's not working.
You need to configure URLSession with background capability. This has always been the case but previously when going into the background it continued to work for a short time (usually upto 3 minutes). It may have gotten more strict in the latest update, or your request takes longer than the time available.
Firstly you need to able background modes in the capabilities tab.
Here is some sample code to show how to setup the URLSession ready for background
private lazy var bgSession: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: Constant.sessionID.rawValue)
//config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
Code sample and further information available from the following article

Firebase Cloud Functions Sending Error

I want to send an error to my iOS Application using Firebase Cloud Functions.
But I don't get any error when I use my url:
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
print(error) // Print nil
}
I have tried all these:
res.status(400).send("error");
res.status(400).send(New Error("error"));
console.error(new Error('A problem occurred'));
res.render({error:...})
Yea there is no way. The error message you send back like res.status(400).send("error"); is in the body itself. If you look at FIRFunctions.m file, it ignores that as it returns a firebase error message instead "Unimplemented" and "Internal" which are totally unconceptual and misleading messages. Like you get unimplemented error for res.status(400).send("error"). So the bottom line, the iOS SDK doesn't support it. You can to write your own localized error messages in your client side which is actually at the end better.

Pattern for retrying URLSession dataTask?

I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.

Request not sent

I'm having a weird problem when i consume my API from my app. Sometimes, for no reason, the request is just not sent, and it fails at the end of the time-out with the following error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."
I have tried many API such as NSURLConnection delegates, NSURLSession and NSURLConnection.sendSynchronousRequest without success.
Here is a sample project i have made to highlight the issue. ConnectionBugApp
Here are the steps to reproduce:
Run the app in Xcode and stop debug just so the app is on your phone
Open the app, click Test Connection (it succeeds, loading wheel stops spinning right after)
Go to other apps like facebook/twitter/network games (somes that are a bit heavy) and switch to airplane mode a few times
Go back to my app and click Test Connection (loading wheel never stops)
A few details that might help:
If I use my server IP instead of my domain name, it succeeds
Issue only appears when on the LTE/4G network
Any ideas or workaround would be greatly appreciated ! Feel free to ask for more details.
Thanks
EDIT
I've edited the description a lot since i first posted it (hoping to make it cleaner and clearer), i'm sorry if some answers or comment don't really make sense anymore.
I have come across this issue when using an asynchronous request. It seems as though iOS limits the number of open connections to a single domain, such that all subsequent connections fail in the manner you have described.
If connections typically complete quickly, this possibly won't be an issue.
The solution is to limit the number of open connections to the same domain to prevent this from happening.
The answer posted by karlos works because the synchronisity of the connection blocks others from being opened.
Like mentioned in comments, I had DNSSEC (cache poisoning protection) enabled on my hosting service.
Disabling it, fixed the issue, even though that might not be a really nice solution. After a few weeks of searching, that'll be good enough.
I'll give the bounty to someone that can explain it, or who can provide a better solution.
In your code your request take default timeout is 60s, but you can change Request time out in your code as below.
in your NetworkItem class change time out.
init(request:NSMutableURLRequest){
self.request = request
self.request.timeoutInterval = 120
super.init()
}
Try the following code for the connection.This would help you.
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
if error != nil || urlData!.length == 0
{
println("Error happend timeout======\(error?.code)!")
continue
}
else //else1
{if let httpResponse = response as? NSHTTPURLResponse
{
println("Status Code for successful---\(httpResponse.statusCode)")
// For example 502 is for Bad Gateway
if (httpResponse.statusCode == 502)
{
// Check the response code from your server and break
break
}
else
{
//Your code
}
}
}
You can get the list of HTTPS status code in the following link
StatusCode

Resources