This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
I am relatively new to Swift so please excuse any rookie errors. I am retrieving some data from a web service and serialising the data into an object. However, when I return this object from the enclosing function, it is always null. If I run this code all in the ViewController however, it works fine. It only seems to fail when I split my code out into separate classes/methods (I am trying to implement better practises). I should also add that no error is printed from the print(error) statement.
Thanks in advance for any help!
func getLocationData(lat: String, long: String) -> Location {
locationUrl += lat + "&lon=" + long
print("Location query: " + locationUrl)
let request = NSMutableURLRequest(url: NSURL(string: locationUrl)! as URL)
request.httpMethod = "GET"
var location: Location?
_ = URLSession.shared.dataTask(with: request as URLRequest, completionHandler:
{(data, response, error) -> Void in
if (error != nil) {
print("Error making requets to web service")
} else {
do {
location = try JSONDecoder().decode(Location.self, from: data!)
} catch let error as NSError {
print(error)
}
}
}).resume()
return location!
}
Your code is asynchronous so location var is nil when you force-unwrap it as the response isn't yet returned , you need a completion
func getLocationData(lat: String, long: String,completion:#escaping (Location?) -> ()) {
locationUrl += lat + "&lon=" + long
print("Location query: " + locationUrl)
var request = URLRequest(url: URL(string: locationUrl)!)
request.httpMethod = "GET"
_ = URLSession.shared.dataTask(with: request as URLRequest, completionHandler:
{(data, response, error) -> Void in
if (error != nil) {
print("Error making requets to web service")
} else {
do {
let location = try JSONDecoder().decode(Location.self, from: data!)
completion(location)
} catch {
print(error)
completion(nil)
}
}
}).resume()
}
Call
getLocationData(lat:////,long:////) { loc in
print(loc)
}
Related
I have a three year old barcode scanning ios app that has been making a synchronous call to an api with no problems until I was forced to use a slighly slower api. The latest version of XCode offer this advice: Synchronous URL loading of https://get-thumb.herokuapp.com/getThumb.php?objectid=20019 should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession.
The code below asychronusly loads an ArtObject with string data including a path to another api. How can I get the data from the second, getThumb api asynchronously and load it into the UI on the main thread?
func downLoadJson (forObject: String) {
let myURLStr = urlObj + forObject
print (myURLStr)
guard let downLoadURL = URL(string: myURLStr) else {return}
URLSession.shared.dataTask(with: downLoadURL) { (data, urlResponse, error) in
guard let data = data, error == nil, urlResponse != nil else {
print ("something is wrong in URLSessions call")
return
}
do {
let decoder = JSONDecoder()
let anObject = try decoder.decode(ArtObject.self, from: data)
/*
print("object Name is \(anObject.ObjectName)")
print("Creators is \(anObject.Creators)")
print("Medium is \(anObject.Medium)")
print("Titles is \(anObject.Titles)")
print("LabelUUID is \(anObject.LabelUUID)")
*/
gArtObject = anObject // this populates the global gArtObject with the local anObject so that the vals are avial in other funcs
var displayText = "ObjName: " + anObject.ObjectName
displayText += "\nCreator(s): " + anObject.Creators
displayText += "\nMedium: " + anObject.Medium
displayText += "\nTitle(s): " + anObject.Titles
displayText += "\nObjectNumber: " + String(anObject.ObjectNumber)
displayText += "\nComponentNumber: " + anObject.compNumber
//--- UI update must happen on main queue THIS IS THE PROBLEM
DispatchQueue.main.async {
self.objectDetails.text = displayText
self.imageVW.setImageFromURl(stringImageUrl: anObject.imageSmall)
}
} catch {
print("something wrong after download step")
print(error.localizedDescription)
}
}.resume()
extension UIImageView{
func setImageFromURl(stringImageUrl url: String){
if let url = NSURL(string: url) {
if let data = NSData(contentsOf: url as URL) {
self.image = UIImage(data: data as Data)
}
}
}
}
You can achieve this the same way you are loading your JSON.
In the setImageFromURl function remove the synchronous call to Data and use:
if let url = URL(string: url) {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.async{
self.image = UIImage(data: data)
}
}
}.resume()
}
I'm trying to make a Log In Call to the backend using Alamofire 5. The problem is when I make the call I need a value to return to the Controller to validate the credentials.
So, the problem is Alamofire only make asynchronous calls so I need to make it synchronous. I saw a solution using semaphore but I don't know how implement it.
This is the solution that I found:
func syncRequest(_ url: String, method: Method) -> (Data?, Error?) {
var data: Data?
var error: Error?
let url = URL(string: url)!
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
let semaphore = DispatchSemaphore(value: 0)
let dataTask = URLSession.shared.dataTask(with: request) {
data = $0
error = $2
semaphore.signal()
}
dataTask.resume()
_ = semaphore.wait(timeout: .distantFuture)
return (data, error)
}
And, this is my request code:
AF.request(request)
.uploadProgress { progress in
}
.response(responseSerializer: serializer) { response in
if response.error == nil {
if response.data != nil {
do {
try decoder.decode(LogInSuccessful.self, from: response.data!)
} catch {
do {
try decoder.decode(LogInError.self, from: response.data!)
} catch {
}
}
}
statusCode = response.response!.statusCode
}
}
I'm trying to improve my error handling when I don't get back any data in my JSON. The function below works fine without any issues except if I catch an error. I'm not sure of how to pass the error to prompt the user or is the way to handle the error if googlebusinessinfo is nil I likely have no data and pass a generic error?
Here's my function:
func getBusinessReviews(googleUrl: String, completion: #escaping (WelcomeReview?) -> ()) {
// Create URL
let url = URL(string: googleUrl)
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
//Set HTTP Header
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Specify HTTP Method to use
request.httpMethod = "GET"
print("(Google)Do we have any data: \(String(describing: data))")
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("(Google)Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Googledata Response data string:\n \(dataString)")
}
if let googlebusinessdata = data {
do {
let googlebusinessinfo = try JSONDecoder().decode(WelcomeReview.self, from: googlebusinessdata)
print("googledata \(googlebusinessinfo)")
completion(googlebusinessinfo)
} catch
{
print("googledata error \(error.localizedDescription)")
let googebusinessinfoerror = error.localizedDescription
}
}
}
task.resume()
}
First of all you can improve the error handling immensely if you print error instead of error.localizedDescription in a Decoding catch block. The former shows the real error, the latter a meaningless generic message.
To answer your question use the generic Result type
func getBusinessReviews(googleUrl: String, completion: #escaping (Result<WelcomeReview,Error>) -> ()) {
// Create URL
let url = URL(string: googleUrl)
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
//Set HTTP Header
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Specify HTTP Method to use
request.httpMethod = "GET"
print("(Google)Do we have any data: \(String(describing: data))")
// Check if Error took place
if let error = error {
print("Error took place \(error)")
completion(.failure(error))
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("(Google)Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Googledata Response data string:\n \(dataString)")
do {
let googlebusinessinfo = try JSONDecoder().decode(WelcomeReview.self, from: data)
print("googledata \(googlebusinessinfo)")
completion(.success(googlebusinessinfo))
} catch
{
print("googledata error:", error)
let googebusinessinfoerror = error.localizedDescription
completion(.failure(error))
}
}
}
task.resume()
}
and call it
getBusinessReviews(googleUrl: "Foo") { result in
switch result {
case .success(let info): print(info)
case .failure(let error): print(error)
}
}
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.
I've been scouring examples looking to pull some ideas together, I've come up with this although I'm not getting any output. It never enters the do which leads me to believe I have an issue with my call.
Can anyone shed some light on this for me or lead me to an appropriate location with more information on API calls in swift 2.0? Examples of this are quite sparse.
let url : String = "http://www.fantasyfootballnerd.com/service/nfl-teams/json/test/"
let request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
print("Start")
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult: NSDictionary! = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print("In method")
if (jsonResult != nil) {
// process jsonResult
print("Data added")
} else {
print("No Data")
// couldn't load JSON, look at error
}
}
catch {
print("Error Occured")
}
}
You're missing just one thing. You need to start the request:
// call this after you configure your session
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// process results
}.resume()