I am having problems finding out why my dataTask returns an empty result.
While going through My NetworkingManager class it appeared that it never enters the URLSession.shared.dataTask. Does anyone know why?
Her is my NetworkingManager which is being used in the ContentView of the app:
class NetworkingManager: ObservableObject {
var didChange = PassthroughSubject<NetworkingManager, Never>()
var showList = ShowResultsAPI(results: []) {
didSet {
didChange.send(self)
}
}
init() {
guard let url = URL(string: "www.json.url") else {
return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else {
return }
let showList = try! JSONDecoder().decode(ShowResultsAPI.self, from: data)
DispatchQueue.main.async {
self.showList = showList
}
}.resume()
}
}
In my opinion your coding looks correct.
Keep in mind that the request is asynch. When your debugging the URLSession.shared.dataTask you will recognize that at first the debugger is skipping the dataTask. When the URLSession receives a response it will enter the URLSession.shared.dataTask again.
I would recommend to set a breakpoint in the line with your guard statement. Then debug the process again and see if the debugger enters the process.
It would also be interesting to observe the response and the error in the completion handler to see if there are errors occuring.
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data = data
else {
print("ResponseProblem")
print(response)
return
}
add return value in guard : return "example"
change your code to this :
}
}.resume()
sleep(2)
}
Adding a .sleep(2) at the end of the init() helped to process the JSON.
Edited:
It needed an asynchronous task which implies having a sleep or as #vadian suggested a better suited delay() from combine.
Related
I am facing an issue when trying to retrieve and decode JSON data from an API. I am able to get the data from the API and decode it successfully, but I can't access it from outside the function scope. I am using the following function to get and decode data. The API returns an array of JSON objects.
func getJSON(completed: #escaping () -> ()) {
var jsonData = [API_data] () // A struct for retrieved data
let url = "URL Here" // I have the original URL here, which I can't share
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self]data,response,error in
guard let data = data, error == nil else{
print("error")
return
}
do{
jsonData = try JSONDecoder().decode([API_data].self,from:data)
} catch{
print(error)
}
print(jsonData[0].id) // This prints my data
})
print(jsonData[0].id) // This won't print my data
task.resume()
}
struct API_data: Codable {
let id : String
}
As a result of this, I cannot use this data anywhere in the application. Any help will be appreciated. I have also tried making jsonData a global variable, updating it in the function and returning it and then using it, still doesn't work.
Thanks for your help.
As you already have a completion handler use it and pass the received data
func getJSON(completed: #escaping ([API_data]) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { print(error); return }
do {
completed(try JSONDecoder().decode([API_data].self,from:data!))
} catch{
print(error)
}
}
task.resume()
}
and use it
getJSON { apiData in
print(apiData[0].id) // This prints my data
}
Or more comfortable with the Result type
func getJSON(completed: #escaping (Result<[API_data],Error>) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { completed(.failure(error)); return }
Result { try JSONDecoder().decode([API_data].self,from: data!) }
}
task.resume()
}
getJSON { result in
switch result {
case .success(let apiData): print(apiData[0].id) // This prints my data
case .failure(let error): print(error)
}
}
It is asynchronous, you might want to access it a bit later when it’s ready, e.g. by using the completion handler.
Try creating a completion handler from your API function and send the data with the completion. And try using it there.
Problem
The data is successfully decoded from Reddit’s api and put into the variable theUser inside the .onAppear {…}, but when I try to use it I keep getting nil values.
Code
struct ContentView: View {
#State var didAppear = false
#State var theUser = getNilUser()
var body: some View {
Text("User Profile")
Text(theUser.data.subreddit.display_name_prefixed ?? "No name found")
.onAppear(perform: {
theUser = getUser(withName: "markregg")
})
}
func getUser(withName username: String) -> user {
if !didAppear {
if let url = URL(string: "https://www.reddit.com/user/\(username)/about.json") {
do {
let data = try Data(contentsOf: url)
do {
let decodedUser = try JSONDecoder().decode(user.self, from: data)
return decodedUser
} catch {
print(error)
}
} catch {
print(error)
}
}
didAppear = true
}
return getNilUser()
}
}
Edit: I’m very new to this so if I made a stupid mistake I’m sorry
You can't get JSON data like that. You have to execute a HTTP request using the URLSession class in order to get the data and parse it.
let username = "markregg"
let url = URL(string: "https://www.reddit.com/user/\(username)/about.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
} else if let data = data {
do {
let decodedUser = try JSONDecoder().decode(User.self, from: data)
} catch {
print(error)
}
} else {
print("Request failed")
}
}.resume()
Another thing to note is that URLSession methods are asynchronous. In simple terms it means they take some time to execute and return the result. So you can't use a simple return statement like return decodedUser to get the result right away. Look into how to use URLSession more.
I am trying to save data from a JSON call into a variable (calling this in ViewDidLoad if it makes a difference). I've been stuck on this for quite a while and am getting pretty frustrated.
I know there are topics on this already, but I can't seem to figure it out and I'm hoping theres a simple explanation for what I'm doing wrong.
Edit: The execution goes to "print("breakpoint")" line before doing the JSON call, I'd like to force execution to wait so I can actually fill up wantedCARDSET but am struggling with how to do that. Sorry if that was unclear!
Here's the call in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
setPickerView.dataSource = self
setPickerView.delegate = self
testJSONgrab.getCards(url: "https://mtgjson.com/json/WAR.json") {json, error in
DispatchQueue.main.async {
for item in json {
self.wantedCARDSET.append(item)
}
}
}
print("breakpoint")
getCards function
class mtgJSONDATA {
func getCards(url: String, completionHandler: #escaping ([CARDS], Error?)-> Void) {
var cardSet = [CARDS]()
guard let url = URL(string:url) else {return}
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error == nil {
guard let data = data else {return}
do {
let decoded = try JSONDecoder().decode(mtgJSON.self, from: data)
DispatchQueue.main.async {
for item in decoded.cards! {
cardSet.append(item)
}
completionHandler(cardSet, nil)
}
} catch let jsonError {
print("Error serializing JSON: ", jsonError)
}
}
})
task.resume()
}
You miss a reload
DispatchQueue.main.async {
self.wantedCARDSET = item
self.setPickerView.reloadAllComponents()
}
As the call to get your data is asynchronous
I've got small function that uses native solutions to parse and decode JSON. Everything works fine, except handling errors. I know that when error occurs, I should get JSON with error and message
func fetchCardDetails(withNumber number: Int) -> Promise<CardDetails> {
guard let URL = URL(string: "\(URLProvider.url)/\(APIKeyProvider.apiKey)/\(number)/") else {
fatalError("Could not reformat string to URL!")
}
var request = URLRequest(url: URL)
request.httpMethod = "GET"
let decoder = JSONDecoder()
let session = URLSession.shared
return Promise { fullfill, reject in
let dataTask = session.dataTask(with: request) { data, response, error in
if let data = data,
let json = (try? decoder.decode(CardDetails.self, from: data)) {
fullfill(json)
} else if let error = error {
reject(error)
} else if let response = response {
print(response)
} else if data != nil {
let data = NSError()
reject(data)
} else {
reject(PMKError.invalidCallingConvention)
}
}
dataTask.resume()
}
}
func fetchCardDetails(number: Int) {
_ = cardDetailsService.fetchCardDetails(withNumber: number).then { cardDetails -> Void in
//some actions with cardDetails
}
}
When getting error - nothing crashes but I am getting error Pending Promise deallocated! This is usually a bug. F.A.Q. from PromiseKit didn't help me though.
Should I maybe decode JSON with error and change whole reject closure?
This part of your if statement neither fulfills or rejects.
else if let response = response {
print(response)
}
If you neither fulfill or reject then the promise never resolves and if an unresolved promise is deallocated it prints the warning you are seeing to the console.
Please see the PromiseKit troubleshooting guide for more information.
I'm building an iOS app in Swift 3 that's supposed to communicate with a JSON Rest Api that I'm also building myself. The app will get all sorts of content from the Api, but for now all I need it to do is check the availability of the Api through a handshake function.
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if jsonResult["response"] as! String == "Welcome, come in!" {
print("************ RESPONSE IS: ************")
print(jsonResult)
return
} else {
return
}
} catch {
print("************ JSON SERIALIZATION ERROR ************")
}
}
}
}
task.resume()
This is the dataTask I've set up and it runs just fine (When I print the jsonResult, I get the "Welcome!" message as expected. The problem is that I want my handshake function to return true or false (So that I can give an alert if the case is false.) When I try to set up a return true or false within the if-statement, I get the error: Unexpected non-void return value in void function.
My question is: How do I return the data out of the dataTask so that I can perform checks with it within my handshake function? I'm very new to Swift so all help is appreciated :)
Below is the entire class:
import Foundation
class RestApiManager: NSObject {
var apiAvailability:Bool?
func handshake() -> Bool {
let url = URL(string: "https://api.restaurapp.nl/handshake.php")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if jsonResult["response"] as! String == "Welcome, come in!" {
print("************ RESPONSE IS: ************")
print(jsonResult)
return true
} else {
return false
}
} catch {
print("************ JSON SERIALIZATION ERROR ************")
}
}
}
}
task.resume()
}
}
Because you're using an asynchronous API, you can't return the bool from your handshake function. If you want to show an alert in the false case, you would replace the return false with something like:
DispatchQueue.main.async {
self.showAlert()
}
Technically you could make the handshake function pause until the network stuff was done, and return the bool, but that defeats the purpose of being asynchronous, and it would freeze your app's UI during the network activity, so I doubt that's what you want to do.