I have a code below which works fine except pull to refresh. It returns cached version of .json. If I use different URL function it reloads new .json on the fly, but if I want to perform pull to refresh with same URL it serving cached version of it.
Thank you
static func loadDataFromURL(url: URL,completion: #escaping (_ data: Data?, _ error: Error?) -> Void) {
let sessionConfig = URLSessionConfiguration.default
sessionConfig.allowsCellularAccess = true
sessionConfig.timeoutIntervalForRequest = 15
sessionConfig.timeoutIntervalForResource = 30
sessionConfig.httpMaximumConnectionsPerHost = 1
let session = URLSession(configuration: sessionConfig)
// Use URLSession to get data from an NSURL
let loadDataTask = session.dataTask(with: url) { data, response, error in
guard error == nil else {
completion(nil, error!)
if kDebugLog { print("API ERROR: \(error!)") }
return
}
guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else {
completion(nil, nil)
if kDebugLog { print("API: HTTP status code has unexpected value") }
return
}
guard let data = data else {
completion(nil, nil)
if kDebugLog { print("API: No data received") }
return
}
// Success, return data
completion(data, nil)
}
loadDataTask.resume()
}
You should set the session.configuration with a URLSessionConfiguration object which config with the cache policy according to your need which I think you haven't set on above code.
Related
I'm trying to retrieve some data from an API, but I got an error: "The given data was not valid JSON ", Status code: 401
I think that is an authentication problem. How can I set the auth credentials to make the GET request?
This is the code for retrieving the data from the JSON.
func loadData()
{
guard let url = URL(string: getUrl) else { return }
URLSession.shared.dataTask(with: url) { data, res, err in
do {
if let data = data {
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
} else {
print(" No Data ")
}
} catch( let error)
{
print(res)
print(String(describing: error))
}
}.resume()
}
This is the code for the view :
struct GetView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.items, id: \.id) { item in
Text(item.year)
}
} .onAppear(perform: {
viewModel.loadData()
})
.navigationTitle("Data")
}
}
}
To handle authentication you must implement a delegate for your URLSession:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(.performDefaultHandling, challenge.proposedCredential)
}
However, your 401 error may be due to your code not sending a valid GET request to the server. You probably want to decode the 'res' value to determine the status code:
if let response = res as? HTTPURLResponse {
if response.statusCode != 200 {
// data may be JSON encoded but you should get some for
// statusCode == 401
}
}
Without knowing the kind of service you are connecting to it is hard to speculate if you need a GET or a POST. The URL you use may require a query parameter.
I found the solution. This is the code for Basic Auth :
func loadData() {
//Credentials
let username = ""
let password = ""
let loginString = "\(username):\(password)"
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
//Request
guard let url = URL(string: getUrl) else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
//Setup Session
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
print(String(data: data, encoding: .utf8)!)
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
}
else {
print(" No Data ")
}
} catch( let error)
{
print(String(describing: error))
}
}
task.resume()
}
I know, there are lots of similar threads regarding asynchronous functions and completion handlers in Swift... But after reading quite a lot of them, I still can't figure out how to process the responded data of a network request and save certain values within global variables.
Here is my code (yes, I'm a Swift rookie):
let equitySymbol = "AAPL"
var companyCountry = String()
func sendRequest(_ url: String, parameters: [String: String], completion: #escaping ([String: Any]?, Error?) -> Void) {
var components = URLComponents(string: url)!
components.queryItems = parameters.map { (key, value) in
URLQueryItem(name: key, value: value)
}
components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
let request = URLRequest(url: components.url!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let response = response as? HTTPURLResponse,
(200 ..< 300) ~= response.statusCode,
error == nil {
let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
completion(responseObject, nil)
}
else {
completion(nil, error)
return
}
}
task.resume()
}
sendRequest("myUrl", parameters: ["token":"..."]) { (responseObject, error) -> Void in
if let responseObject = responseObject, error == nil {
companyCountry = responseObject["country"] as! String
}
else {
print(error ?? "Details Not Available")
return
}
}
print(companyCountry)
As you may assume, my variable "companyCountry" is still nil after my func "sendRequest" has been called (playground), when it should be "US" in this case. What's my mistake? Thanks a lot for your help!!!
A successful network request can take a long time. Usually it's much less than a second, but up to 60 seconds is possible. Even if it is just a millisecond, you are printing companyCountry a loooooong time before the callback function of the network request is called.
If you want to print companyCountry for debugging purposes, print it in the callback. If you want to keep it, store it into a permanent location in the callback.
so i wanna parse json api, but i the way i get that param to parse i need to fetch another json (which is working), and since i cant put that data param for my 2nd json api into global var so i can just put it into another func, i have this idea that i parse my 2nd json api inside the 1st urlSession, but i always get a nil callback,
override func viewDidLoad() {
super.viewDidLoad()
getRoom()
}
func getRoom() {
guard let url = URL(Some url) else {return}
print(url)
URLSession.shared.dataTask(with: url) { data, resp, err in
guard let data = data else {return}
do{
let decoder = JSONDecoder()
let room = try decoder.decode(User.self, from: data)
self.dataClient = [room].compactMap{$0!.data}
self.DATA = [room]
print("ini dataClient 🪕\(self.dataClient)")
let roomid = self.dataClient[0].RoomID
self.roomId = roomid
print(self.roomId)
DispatchQueue.main.async {
checkRoom()
}
}catch{
print(err!)
}
}.resume()
}
func checkRoom() {
if self.roomId == 0 {
print("roomId nil")
}else if self.roomId != 0{
print("ini room id \(self.roomId)")
guard let urlRoom = URL(some url) else {return
URLSession.shared.dataTask(with: urlRoom) { (data, resp, err) in
guard let data = data else {return}
do{
let decoder = JSONDecoder()
let roomAv = try decoder.decode(User.self, from: data)
self.DATA = [roomAv]
print("ini boolnya 🎸 \(self.DATA[0].success)")
print("Success")
}catch{
print("damnðŸ˜") // this line always get called
}
}.resume()
}
}
can anyone tell me any ideas? the reason i put the 2nd urlsession inside 1st urlsession because i need that (self.roomId) for my param in my 2nd Json api.
and when i try to separate both urlsession func in my checkRoom() alwasy called "roomId Nil"
I wouldn't make a call within a call personally. That's asking for trouble. Just call the first endpoint, get the data from it and pass in whatever you needed from that into the second call in your logic controller.
Quasi code:
import Foundation
class Test {
func getRoom() {
getFirstCall { [weak self] (foo) in
self?.getSecondCall(someArg: foo) {
// Handle data here.
}
}
}
func getFirstCall(completion: #escaping (_ somethingToReturn: String) -> ()) {
guard let url = URL(string: "Some URL") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
// Logic to ingest data.
completion("foo")
}.resume()
}
func getSecondCall(someArg: String, completion: #escaping () -> ()) {
guard let url = URL(string: "Some URL 2") else { return }
// Use "someArg" however you need in this call. queryParam, body, etc.
URLSession.shared.dataTask(with: url) { data, response, error in
// Logic to ingest data.
completion()
}.resume()
}
}
i did read a lot about functions with completion-handler, but now i have a problem how to call this function (downloadJSON) in the correct way. Which parameters do i have to give in the function and handle the result-data (json) in my own class, where the function was called.
This is the code from David Tran. Hi makes wonderful tutorials, but in the code there is no call of this function.
let request: URLRequest
lazy var configuration: URLSessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = URLSession(configuration: self.configuration)
typealias JSONHandler = (JSON?, HTTPURLResponse?, Error?) -> Void
func downloadJSON(completion: #escaping JSONHandler)
{
let dataTask = session.dataTask(with: self.request) { (data, response, error) in
// OFF THE MAIN THREAD
// Error: missing http response
guard let httpResponse = response as? HTTPURLResponse else {
let userInfo = [NSLocalizedDescriptionKey : NSLocalizedString("Missing HTTP Response", comment: "")]
let error = NSError(domain: DANetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
completion(nil, nil, error as Error)
return
}
if data == nil {
if let error = error {
completion(nil, httpResponse, error)
}
} else {
switch httpResponse.statusCode {
case 200:
// OK parse JSON into Foundation objects (array, dictionary..)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any]
completion(json, httpResponse, nil)
} catch let error as NSError {
completion(nil, httpResponse, error)
}
default:
print("Received HTTP response code: \(httpResponse.statusCode) - was not handled in NetworkProcessing.swift")
}
}
}
dataTask.resume()
}
Let Xcode help you. Type downlo and press return. Xcode completes the function
Press return again and you get the parameters
You have to replace the placeholders with parameter names for example
downloadJSON { (json, response, error) in
if let error = error {
print(error)
} else if let json = json {
print(json)
}
}
Note:
There is a fatal type mismatch error in your code: The result of the JSONSerialization line is [String:Any] but the first parameter of the completion handler is JSON
i created a watchOS app that request a value from an API and show it on a label.
It is working perfectly in the simulator but when I execute it on my Apple Watch it crashes with the following error:
[ERROR] There is an unspecified error with the connection
fatal error: unexpectedly found nil while unwrapping an Optional value
The first error is generated by my code.
The code I wrote is:
func price_request() -> NSData? {
guard let url = NSURL(string: "https://api.xxxxx.com/xxx.php") else {
return nil
}
guard let data = NSData(contentsOfURL: url) else {
print("[ERROR] There is an unspecified error with the connection")
return nil
}
print("[CONNECTION] OK, data correctly downloaded")
return data
}
func json_parseData(data: NSData) -> NSDictionary? {
do {
let json: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! Dictionary<String, AnyObject>
print("[JSON] OK!")
return (json as? NSDictionary)
} catch _ {
print("[ERROR] An error has happened with parsing of json data")
return nil
}
}
I tried also to add the App Transport Security bypass also if it is not needed because of a request to an HTTPS URL but it does not works.
Can you please help me?
Thank you
Try using NSURLSession to get data...
//declare data task
var task: URLSessionDataTask?
//setup the session
let url = URL(string:"https://url.here")!
let session = URLSession(configuration: URLSessionConfiguration.default)
task = session.dataTask(with: url){ (data, res, error) -> Void in
if let e = error {
print("dataTaskWithURL fail: \(e.localizedDescription)")
return
}
if let d = data {
//do something
}
}
task!.resume()