This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 5 years ago.
I am trying to get a string out from an external JSON file, which is on a web server, and it gets it successfully, but it is inside a closure where it gets the value, and I need to get it outside so I can return it with the variable returnip How do I do this?
func getJsonFromUrl() -> String {
let URL2 = "https://url.com/asd.php";
let url = URL(string: URL2)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let ips = parsedData["ip"] as! String
print("The IP is: " + ips) //Prints the value correctly
var returnip = ips //The value that I want to return, that does not go outside this closure
} catch let error as NSError {
print(error)
}
}
}.resume()
return returnip //Does not return anything
}
Thanks
You cannot return from an asynchronous function and a return statement inside a closure only returns from the closure itself.
You need to use a completion handler instead. Also, don't use force unwrapping of optionals/force casting optionals when parsing a network response.
func getJsonFromUrl(name: String, completion: #escaping (String?)->()) {
//use name variable just as you would in a normal function
let URL2 = "https://url.com/asd.php"
let url = URL(string: URL2)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
completion(nil)
} else {
do {
guard let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] else { completion(nil); return }
guard let ips = parsedData["ip"] as? String else {completion(nil); return }
print("The IP is: " + ips) //Prints the value correctly
completion(ips)
} catch let error as NSError {
print(error)
completion(nil)
}
}
}.resume()
}
Then you can call it like this:
getJsonFromUrl(name: "Input", completion: { ips in
print(ips)
//you can only use the value inside the closure of completion
})
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I'm trying to return json data from an api call. I'm able to access the json data successfully but am struggling to find a way / the best way to return it for access in my app. Thanks for any ideas!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// make the api call and obtain data
let data = self.loadData()
print("inside viewDidLoad", data) // prints 'inside viewDidLoad emptyString'
}
func loadData() -> String {
var circData = "emptyString"
let session = URLSession.shared
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) {
// print("json: ", json) // prints the whole json file, verifying the connection works. Some 300kb of data.
// print("json file type: ", type(of: json)) // prints '__NSArrayI'
let jsonString = "\(json)"
circData = jsonString
// print("circData", circData) // prints the whole json file, verifying that the json string has been assigned to 'circData'
}
})
task.resume()
// print("after: ", circData) // prints 'after: emptyString'. It's as if the reassignment didn't take place.
return circData
}
}
You can't return a value synchronously becuase the api call that is fetching json data is asynchronous. You need to use a completion handler instead.
You can put breakpoints in different places inside the code to understand how the flow executes.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.loadData(completion: { [weak self] (result, error) in
if let error = error {
print(error.localizedDescription)
}
if let result = result {
print(result)
}
})
}
func loadData(completion: #escaping (_ data: Any?, _ error: Error?) -> Void) {
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let error = error {
completion(nil, error)
return
}
do {
if let data = data {
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments])
completion(json, nil)
} else {
completion(nil, nil)
}
} catch {
completion(nil, error)
}
})
task.resume()
}
}
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()
}
}
So I know how to parse JSON and retrieve a JSON from a URLRequest. What my objective is to remove this JSON file so I can manipulate it into different UIViewControllers. I have seen some stuff with completion handlers but I run into some issues, and I haven't fully understand. I feel like there is a simple answer, I am just being dumb.
How can I take this JSON outside the task and use it in other Swift files as a variable?
class ShuttleJson: UIViewController{
func getGenres(completionHandler: #escaping (_ genres: [String: Any]) -> ()) {
let urlstring = "_________"
let urlrequest = URLRequest(url: URL(string: urlstring)!)
let config = URLSessionConfiguration.default
let sessions = URLSession(configuration: config)
// request part
let task = sessions.dataTask(with: urlrequest) { (data, response, error) in
guard error == nil else {
print("error getting data")
print(error!)
return
}
guard let responseData = data else {
print("error, did not receive data")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any]{
//Something should happen here
}
print("no json sucks")
}
catch{
print("nah")
}
}
task.resume()
}
}
First of all remove the underscore and the parameter label from the completion handler. Both are useless
func getGenres(completionHandler: #escaping ([String: Any]) -> ()) {
Then replace the line
//Something should happen here
with
completionHandler(json)
and call the function
getGenres() { json in
print(json)
}
Notes:
The check guard let responseData = data else is redundant and it will never fail. If error is nil then data is guaranteed to have a value.
You should print the caught error rather than a meaningless literal string.
I am trying to get the variable from the competition on a function, and turn it into a global variable. This is the function I'm calling:
func getJsonFromUrl(name: String, completion: #escaping (String?)->()) {
//use name variable just as you would in a normal function
let URL2 = "https://url.com/asd.php"
let url = URL(string: URL2)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
completion(nil)
} else {
do {
guard let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] else { completion(nil); return }
guard let ips = parsedData["ip"] as? String else {completion(nil); return }
print("The IP is: " + ips) //Prints the value correctly
completion(ips)
} catch let error as NSError {
print(error)
completion(nil)
}
}
}.resume()
}
This is how I call it:
getJsonFromUrl(name: "Bob", completion: { ips in
print(ips)
})
However, I want to turn ips from where I call it to a global variable. Currently, I have a variable stated outside the ViewController called ip2, and this is how I tried setting the variable:
getJsonFromUrl(name: "Bob", completion: { ips in
print(ips)
ip2 = ips!
})
And it's not setting ip2 to the value of ips. How can you do this?
I think the problem because the URLSession is async task. try this it might can help :
change the way you define ip2 variable like this :
var ip2 : String = "" {
didSet {
//Do something when ip2 get the value
}
}
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.