How to solve issue with getting Data using URL Session? - ios

I am using URL Session to get Data from a web API and load them in a tableView.
Every thing is working fine except that whenever I get the data the first time, the data is always returned the same even if it was changed in the backend side.
The data is only returned new when I delete the application and install it again.
Any idea on how to solve this please?

If you are saying that when deleting the application your problem is solved then the issue is in caching.
To solve this either add:
URLCache.shared.removeAllCachedResponses()
or:
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalCacheData
let session: URLSession = URLSession(configuration: config)
This will solve your problem and ignore caching so that the data will be returned new everytime.

Related

Reload data of a json file after being updated

after hours of search I need your help. I have setup a program for iOS showing recipes of my private cooking book. The recipes are stored in a json file on my web server. I use another application on my MAC to update the recipes in this json file and to store it on my web server. Unfortunately the updated recipes are not shown in my iOS-application. The iOS-app shows always the old data - I guess they are stored locally on my iPhone/iPad after the first installation of the app?
How can I enforce that at every launch of the iOS-app on my iPhone/iPad the actual json data from the web server are used and not the old one.
I retrieve the data via this function I call from the viewDidLoad() and before the reloadData() of the UITableView showing the titles of the recipes
func initKochbuch() {
let url = URL(string: selektiertesKochbuch)!
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: url) { (data, response, error) in
guard let data = data else {
debugPrint("Fehler beim Laden", error ?? "Unbekannter Fehler")
return
}
self.kochbuch_local.rezepte = try! JSONDecoder().decode([Rezept].self, from: data)
self.initRezeptAnsicht()
OperationQueue.main.addOperation {self.tableView.reloadData()}
}
task.resume()
}
What do I have to do in addition? Thanks for your support.
Few possibilities:
hope you are not storing the data locally(coredata/plist/any file), and loading it anywhere in your app.
Your web server might be caching the old data, please check once.
See if you have any hardcoded sample data which was used for testing purpose but not removed after actual implementation.
Hope this helps.

What's the correct usage of URLSession, create new one or reuse same one

I am using URLSession in my iOS project. (Swift 4). The following code is only for illustration purpose.
class MyTaskManager {
...
func postMyData(...) {
let defaultSession = URLSession(configuration: .default)
dataTask = defaultSession.dataTask(with: url) { data, response, error in
...
}
dataTask.resume()
}
func getMyData(...) {
let defaultSession = URLSession(configuration: .default)
dataTask = defaultSession.dataTask(with: url) { data, response, error in
...
}
dataTask.resume()
}
}
I am trying to understand the best practice of using URLSession in the sense of whether each function call of making HTTP request should create a new URLSession or should I create a global one & all the calls to HTTP requests should use the same URLSession instance?
I have studied on internet, there is an accepted answer which says I should create a new URLSession for each function/request call , there is/are also suggestions that I should reuse the same URLSession. I get confused by those accepted but conflicting answers. Could someone clarify for me the correct answer to this question?
My application doesn't have upload or download tasks, only pure RESTful request with JSON data format. No multiple configurations needed either.
You should create a shared instance of the data session and use the same creating multiple tasks because it's rarely the case that you need to have a different configuration for an api.
I suggest and use the shared instance of data session for getting data from an api.
class MyTaskManager {
static let sessionManager: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30 // seconds
configuration.timeoutIntervalForResource = 30 // seconds
return URLSession(configuration: configuration)
}()
func postMyData(...) {
dataTask = sessionManager.dataTask(with: url) { data, response, error in
...
}
dataTask.resume()
}
func getMyData(...) {
dataTask = sessionManager.dataTask(with: url) { data, response, error in
...
}
dataTask.resume()
}
}
The benefit of this is that I had to create the session only once, so that will save repetition of same code and also the process to initialise the same thing again per api request. This will be more helpful in case you need to more custom configuration of the session.
Most of the time, you should use a single session for all of your work. This allows the session to limit simultaneous requests to a single host (limiting the potential for accidental abuse), and also is significantly more memory efficient than using a new session for each request.
If you have multiple groups of tasks that need to be canceled as a group (for example, uploading all of the images in a new album, downloading all the resources for loading a single web page, etc.), or multiple requests that need a different configuration (e.g. background downloads) then it makes sense to use one session per group or configuration.
Alternatively, if you don't need to make any configuration changes to the default and are happy with just running a block when each request finishes, you can also use the shared session ([NSURLSession sharedSession]) rather than creating a session at all.
-It depends on your usage of the URLSession Object.
-You will create a new one if you need to create your configuration & assign a delegate.
-You will use the shared instance if you won't change neither the configuration nor setting a delegate.
Also this part from the Apple documentation regarding the shared instance limitation:
the shared session has important limitations:
-You can’t obtain data incrementally as it arrives from the server.
-You can’t significantly customize the default connection behavior.
-Your ability to perform authentication is limited.
-You can’t perform background downloads or uploads when your app isn’t running.

WKWebView, get all cookies

I want obtain all cookies from WKWebView. Why? I have been started a project that use web-based auth. As result, I should intercept cookies to be sure that user is logged in and for some other purposes. Another case - imagine if user logged in, and than he "kill" the app - due to some delay in storing this cookie session will be lost :(.
The problem seems to be that the cookies are cached and not saved out
to a file immediately.
(#Kemenaran from here - p.5 below)
The point where I try to catch them -
webView:decidePolicyForNavigationResponse:decisionHandler:,
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
if let httpResponse = navigationResponse.response as? NSHTTPURLResponse {
if let headers = httpResponse.allHeaderFields as? [String: String], url = httpResponse.URL {
let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url {
for cookie in cookies {
NSHTTPCookieStorage.shared.set(cookie)
}
}
}
}
but not all request are navigation, so one cookie (in my case) is skipped, see details below
Few words about other option I tried...
Yes, i Know that starting from iOS 11, we can use WKHTTPCookieStore as mention here. But my project should support iOS 9+
I for 100% sure, that after 5-10 sec from login, required cookie will be saved to NSHttpCookieStorage (at least all my tests during few days confirm that)
I try to use provided observer NSHTTPCookieManagerCookiesChangedNotification, but it provide me callback only for cookies that comes within webView:decidePolicyForNavigationResponse:decisionHandler
I also try to get cookies using some JS like mentioned here and also test all suggestion from here - really great article by the way. Result - negative
I also found this radar bug, and this SO question, and Sample project, but I want to prevent even this case. (described in this post applicable not only for remove but and for save) Also this situation true and when user kill the app, so case when user login, kill app and relaunch, may be present. And preventing this (simply by checking NSHttpCookieStorage for required cookies are also not good idea, because exactly after login required cookie can be stored with some delay, so this approach requires some bool-powered solution, that looks like weird..
I also read few more SO post for some related problem, and the most usefull are
This one
Another one
One more
But still without good solution...
So, is any way exist to obtain or at least force to immediately store cookies?
I ended with simple "force-like" saving Cookie from webpage.
To get all cookie i use
stringByEvaluatingJavaScriptFromString
with JS string like document.cookie();. As result i able to receive all cookies as a string with ; separator. All i need to do - parse string, create cookie and set it to NSHttpSharedStorage

CredStore Perform Query error

I am running into an issue while doing API calls to my apps backend, every connection now prompts with
CredStore - performQuery - Error copying matching creds. Error=-25300, query={
atyp = http;
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = http;
"r_Attributes" = 1;
srvr = "myappsurl.com";
sync = syna;
}
I am a little lost as I am not sure what is causing this, or what CredStore even does.
What purpose does CredStore serve in iOS?
This error occurs when trying to retrieve an URLCredential from URLCredentialStorage for an unknown URLProtectionSpace.
e.g.
let protectionSpace = URLProtectionSpace.init(host: host,
port: port,
protocol: "http",
realm: nil,
authenticationMethod: nil)
var credential: URLCredential? = URLCredentialStorage.shared.defaultCredential(for: protectionSpace)
produces
CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = http;
"r_Attributes" = 1;
srvr = host;
sync = syna;
}
Give it a credential for the protection space:
let userCredential = URLCredential(user: user,
password: password,
persistence: .permanent)
URLCredentialStorage.shared.setDefaultCredential(userCredential, for: protectionSpace)
and the error goes away next time you try to retrieve the credential.
I am a little lost as I am not sure what is causing this, or what
CredStore even does. What purpose does CredStore serve in iOS?
Credential storage on iOS allows users to securely store certificate-based or password-based credentials on the device either temporarily or permanently to the keychain.
I suspect that you have some sort of authentication on your backend server and that server is requesting an authentication challenge to your app (for which no credential exists).
It can probably be safely ignored as returning nil from the URLCredentialStorage is a valid response
I'm not sure why do we get this error when perform requests with Alamofire, but if you do API requests with some token in HTTP headers, you maybe don't need credentials store at all. So we can disable it for our request:
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = ourHeaders
// disable default credential store
configuration.urlCredentialStorage = nil
let manager = Alamofire.SessionManager(configuration: configuration)
...
No errors after such change.
This same issue happens to me and I found that if your API URL does not contain a "/" at the end of URL then iOS does not send "Authorization" value to the server. Due to which you will see a message like posted in question in the console.
So Simply add "/" at the end of URL
https://example.com/api/devices/
In my case, I was not initialising Stripe SDK with API key.
STPPaymentConfiguration.shared().publishableKey = publishableKey
In case of any Stripe operation, we can print the error log, its easy to understand.
print(error.debugDescription)
This is transport error, let's add transport permission like this in plist file:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Be careful as that enables connection to any server from your app. Read more on App Transport Security before proceeding. See comment by #kezi
I edited the String that contains the URL to fix this issue:
var myUrl = "http://myurl.com"
myUrl = myUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!
let url = URL(string: myUrl)
If you get this error, when using AVPlayer, just call .play() on main thread
The cause of me getting this error was due to me accidentally using two spaces between the "Bearer" and access token in my Authorization header.
Incorrect:
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
Correct:
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
Simple mistake, but it took a while to find it.
OK, I had this error, and fought with it for a long time (years) when interacting with my Ruby on Rails app.
I had default credentials set up as described in the accepted answer, but still got the error, and have been relying on a didReceiveChallenge response to supply the credentials - fortunately that worked as a work around.
But! I've just found the solution!
I was working on a hunch that that the protectedSpace fields did not match the Authorization challenge from the Ruby on Rails server - and I looked into the realm field, which seemed to be the only one that was being left undefined.
I started by printing out the server response headers, and although I was able to examine these, they did not include the WWW-Authorization field that would have included the realm field.
I thought this was maybe because my Rails app wasn't specifying the realm, so I started looking at the Rails side of things.
I found I could specify the realm in the call to,
authenticate_or_request_with_http_basic
...which I am using for HTTP Basic authentication.
I wasn't specifying a realm already, so added one,
authenticate_or_request_with_http_basic("My Rails App")
I then added the corresponding string to the protectionSpace,
NSURLProtectionSpace *protectionSpace =
[[NSURLProtectionSpace alloc] initWithHost:#"myrailsapp.com"
port:443
protocol:NSURLProtectionSpaceHTTPS
realm:#"My Rails App"
authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
Voila! That worked, and I no longer get the,
CredStore - performQuery - Error copying matching creds. Error=-25300
Even after specifying the realm in the Rails app, I still don't see it passed in the HTTP header, I don't know why, but at least it works.
The error may also be caused by a Content Security Policy (CSP) that may be too restrictive. In our case, we needed a CSP that is more or less completely open and allows everything. Keep in mind that opening the CSP can be a great security issue (depending on what exactly you're doing in the app).
I got this issue when I tried to open a http-page inside a web-view. But this page contained an popup which was opened first.
When backend team removed this popup everything became OK.
My issue was base64 encoding of an image that was being sent with a rest call
I had previously used
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
But 50% of the time I would get the error above.
I used the following instead which solved my problem...
let strBase64 = imageData.base64EncodedString()
Had the same issue with Twitter sign in. Turned out I used the the wrong API key.
I remove .cURLDescription
on
AF.request(url)
and that log is gone
When working with the Stripe IOS SDK, I found that I should have added the publishable key from stripe.
This is set in AppDelegate, as found in https://stripe.com/docs/development/quickstart, step 2.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
StripeAPI.defaultPublishableKey = "pk_test_....."
return true
}
let credentialData = "\(user):\(password)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Authorization": "Basic \(base64Credentials)"]
Alamofire.request(url, method: .get, parameters: params,encoding: URLEncoding.default,headers: headers)
.responseJSON{
response in
guard let value = response.result.value else {return}
print(value)
}

Swift clearing JSON cache?

I'm working with some API data that gets updated frequently.
I recently discovered that the data does not update properly on the phone, when it's updated on the server.
After hours on hours trying to troubleshoot this, I finally simply tried deleting the app from my phone, and reinstalling in. And it worked.
After some further testing I discovered that it's printing out old JSON.
Once I delete the app and reinstall it, it successfully prints out the correct updated JSON.
From that I gathered that it's probably an issue with the phone caching the old JSON data somehow.
So how can I go about clearing this cache in swift? or forcing it to make a new request.
(I'm using swiftyJson, although I don't think that has anything to do with this specific problem)
I did find one other question like this, but it's old (2014 in Obj-C) and there was no answers.
Here's how I get my data:
var request = NSURLRequest(URL: formulaAPI!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
var formula = JSON(data: data!)
// Loop through the api data.
for (index: String, portfolio: JSON) in formula["portfolio"] {
// Save the data into temporary variables
tempStockName = portfolio["name"].stringValue
tempTicker = portfolio["ticker"].stringValue
tempPurchasePrice = portfolio["purchase_price"].floatValue.roundTo(2)
tempWeight = portfolio["percentage_weight"].floatValue
latestAPIPrice = portfolio["latest_price"].floatValue.roundTo(2)
tempDaysHeld = portfolio["days_owned"].intValue
// Continues on for quite a while, but the data in the above segment is definitely getting filled with old JSON data, so the issue is arising before this point
}
I tried changing my request to the following:
var request = init(formulaAPI: NSURL, cachePolicy: NSURLRequestCachePolicy, timeoutInterval: NSTimeInterval)
But that causes an error: "Use of local variable 'request' before it's declaration"
Any help figuring this out would be greatly appreciated!
Instead of creating your request with,
NSURLRequest(URL: formulaAPI!)
you should use the following method so that you can explicitly set the cache policy.
var request = NSURLRequest(URL: formulaAPI!, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 30)
NSURLRequest(URL:) uses the default came policy, NSURLRequestUseProtocolCachePolicy, and a time out interval of 60 seconds.

Resources