Swift Function Not Continuing after completion of Json Decoder within - "else if" issue? - swift

I am using this function to call json information from a database - it gets the information fine.
But it does not continue after the "getHistoricalMonthlyData". so it will not get to the print("****** line 55"). (yes, I plan on making this a func once I figure out the issue.)
it will print the "print(i.stock)" fine.
I can share the "getHistoricalMonthlyData" code but it works fine and I doubt that is the issue.
I am not great with the completion handlers and I suspect that is the issue?
below is the "getHistoricalMonthlyData" function that I can not get past.
func calculateMonthPerformance (setting: settings) {
let set = setting
let u = User.getUser()
var i = Indexes()
getHistoricalMonthlyData(symbol: symbol, beg: set.monthBeg, end: set.monthEnd) { (json, error ) in
if let error = error {
print ("error", error)
} else if let json = json {
print ("success")
i.stock = json
print(47)
}
// this is fine
print(50)
print(i.stock)
}
// nothing at this point
print("****** line 55")
}
This is how the json function is set up and works great in another project.
it has a resume.
func getHistoricalMonthlyData(symbol: String, beg: Date, end: Date, completionHandler: #escaping ([HistoricalData]?, Error?) -> Void) {
let beg = beg.dateAtStartOf(.month).toFormat("yyyy-MM-dd")
let end = end.dateAtEndOf(.month).toFormat("yyyy-MM-dd")
let jsonUrl = "https://eodhistoricaldata.com/api/eod/\(symbol).US?from=\(beg)&to=\(end)&api_token=\(EOD_KEY)&period=eom&fmt=json"
guard let url = URL(string: jsonUrl) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not create URL")
completionHandler(nil, error)
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error!)
return
}
guard let jasonData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
do {
let historical = try JSONDecoder().decode([HistoricalData].self, from: jasonData )
completionHandler(historical, nil)
} catch let jsonErr {
print ("Error serializing json", jsonErr )
let error = BackendError.objectSerialization(reason: "Couldn't create a todo object from the JSON")
completionHandler(nil, error)
}
}.resume()
}
thanks.

if someone knows a better answer would love to hear about it.
I added a DispatchSemaphore to the code and it seems to work.
cheers.
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error!)
return
}
guard let jasonData = data else {
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
do {
let historical = try JSONDecoder().decode([HistoricalData].self, from: jasonData )
completionHandler(historical, nil)
} catch let jsonErr {
let error = BackendError.objectSerialization(reason: "Couldn't create a todo object from the JSON")
completionHandler(nil, error)
}
semaphore.signal()
}.resume()
_ = semaphore.wait(timeout: .distantFuture)

Related

Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)

Below line of code is producing the error,
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
When print jsonData This code returns perfect result of array but getting this error,
Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)
As the error says I understand its a cast exception, but I'm not able to modify the code to make it work. I'm kinda new to Swift, so any help would be appreciated. Below is my
import Foundation
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<Any, Error>, Error) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error), Error.self as! Error)
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response")
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data")
return
}
do {
// Parse the data
let decoder = JSONDecoder()
let jsonData = try decoder.decode(APIService.self, from: data)
// print(jsonData)
// Back to the main thread
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
} catch let error {
completion(.failure(error),error)
}
}
dataTask?.resume()
}
}
Modify the completion block parameters, you already are returning the error inside the Result's .failure(Error) block so no need to repeat it again as another parameter in the completion parameter. Here's how you fix this:
Declaration:
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<CategoriesNewsData, Error>) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error))
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response") // Throw a custom error here too.
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data") // Throw a custom error here too.
return
}
do {
let decoder = JSONDecoder()
let jsonData = try decoder.decode(CategoriesNewsData.self, from: data)
DispatchQueue.main.async {
completion(.success(jsonData))
}
} catch let error {
completion(.failure(error))
}
}
dataTask?.resume()
}
}
Calling:
service.getPopularPosts { result in
switch result {
case .success(let categoriesNewsData):
print(categoriesNewsData)
case .failure(let error):
print(error)
}
}

Swift scoping outside of a function

I have a singleton URLSession that is parsing the response data into a dictionary. I want to use a single value from that dictionary in a subsequent piece of code, but cannot figure out how to pass the value out from the scope it's currently in.
Here is the code as it stands now:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
return
}
guard let content = data else {
debugPrint("No data")
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
return
}
if let idToken = json["id_token"] as? String {
let privateToken = idToken;
debugPrint("Gotten json response dictionary is \(idToken)")
}
}
task.resume()
return privateToken
Currently there is an IDE error on return privateToken saying that I am using an unresolved identifier: privateToken.
How can I take the string idToken and return it as a privateToken for use elsewhere?
Could you use a completion handler like:
func getPrivateToken(completion: #escaping(String) -> (), failure: #escaping (Error) -> ()) {
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
failure(error)
return
}
guard let content = data else {
debugPrint("No data")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
if let idToken = json["id_token"] as? String {
completion(idToken)
debugPrint("Gotten json response dictionary is \(idToken)")
}
}.resume()
}
And use it like so:
func exampleFunction() {
self.getPrivateToken(completion: { (token) in
// Do what ever you need with the token here.
print("ID token is: \(token)")
}) { (error) in
// Present error here
}
}

Swift function produces a "SIGILL" on it's return statement

I am writing a piece of code in Swift to hit a public API endpoint to pull back data in JSON and use it in the application. I am using URLSession to do the request and am using an async/await similar paradigm to extract data out of the URLSession callback and place it in a local variable. Then, the function returns the optional dictionary returned by JSONSerialization to the caller.
This code executes perfectly fine outside of a function and run as part of the main program, but as soon as it is moved to a function, the return statement produces a "SIGILL" exit.
I breakpointed to the return statement and found that it is exactly what is throwing this error. Since this is an optional dictionary, I tried just returning an unwrapped version of the dictionary and found the same results. I also tried just returning a blank dictionary and I still get a SIGILL
Functioning:
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
print(tenant!["tenant_name"]!)
Fails:
let _ = HttpHelper.getTenantFor(tenantId: <someUUID>)
class HttpHelper {
static func getTenantFor(tenantId: String) -> [String:Any]? {
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
return [String:Any]()
}
}
On the functioning code, the app outputs the proper value for the "tenant_name" key in the JSON object and in the failed code I get the following:
Process finished with exit code 132 (interrupted by signal 4: SIGILL)

Completion handler only gets called if it wasn't called before

My application receives location updates, and when it does (if they are relevant), I call an API asynchronously using a completion handler. When the application opens, the completion handler responds only if there was no request that finished before (two requests come in at the same time usually). When I debug, after the first 2-3 requests (which come in at the same time) where everything works, when the location update passes as relevant, the whole completion handling part of code gets skipped.
This is how I call the completion handler:
if conditions {
let lat = Float(loc.lat)
let long = Float(loc.long)
// calls function using completion handler in order to add new location
BusStations.allBusStations(lat: lat, long: long) { (busStations, error) in
if let error = error {
// got an error in getting the data
print(error)
return
}
guard let busStations = busStations else {
print("error getting all: result is nil")
return
}
if !busStations.stops.isEmpty || self.locations.isEmpty {
// do stuff
}
}
}
This is how I make the API call:
static func allBusStations (lat: Float, long: Float, completionHandler: #escaping (BusStations?, Error?) -> Void) {
let endpoint = BusStations.endpointForBusStations(lat: lat, long: long)
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let stations = try decoder.decode(BusStations.self, from: responseData)
completionHandler(stations, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
What am I doing wrong? Any help would be appreciated.
I would try to dispatch the completion handler to global or main queue to see if it is deferred by system to execute on a queue of lower levels.

Why isn't my Weather Underground data not printing in Swift 3?

I'm having an issue printing data from Weather Underground. My code works with other data sources, just not Weather Underground. I have even tried replacing the URL with actual data (i.e. https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/94129.json"), but it doesn't print.
Any suggestions on what it could be?
import Foundation
import UIKit
class APIManager {
func weatherJSON(zip: String, completion: #escaping ([Weather]) -> Void) {
let baseUrlString = "https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/\(zip).json"
guard let url = URL(string: baseUrlString) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil, let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] else { return }
// MARK: Print JSON
print(json)
var weatherList = [Weather]()
for item in json {
if let weather = Weather.create(from: item) {
weatherList.append(weather)
}
}
completion(weatherList)
} catch {
print("Uh oh. You have an error with \(zip)!")
}
}
task.resume()
}
}
EDIT: SOLVED
I have used the code posted below and am now seeing errors.
I'd suggest changing this to report errors:
enum WeatherError Error {
case badURL
case invalidJSON
}
func weatherJSON(zip: String, completion: #escaping ([Weather]?, Error?) -> Void) {
let baseUrlString = "https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/\(zip).json"
guard let url = URL(string: baseUrlString) else {
completion(nil, WeatherError.badURL)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil, let data = data else {
completion(nil, error)
return
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] else {
completion(nil, WeatherError.invalidJSON)
return
}
// MARK: Print JSON
print(json)
var weatherList = [Weather]()
for item in json {
if let weather = Weather.create(from: item) {
weatherList.append(weather)
}
}
completion(weatherList, nil)
} catch let parseError {
print("Uh oh. You have an error with \(zip)!")
if let responseString = String(data: data, encoding: .utf8) {
print("responseString = \(responseString)")
}
completion(nil, parseError)
}
}
task.resume()
}
Then, when you call it, you can see what the error was
weatherJSON(zip: something) { weatherReports, error in
guard let weatherReports = weatherReports, error == nil else {
print(error)
return
}
// use weatherReports here
}
This won't solve your problem, but it will help you diagnose what the issue is.