This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I am developing an application that json parse. I'm using the AlertView for json messages. But I can not access the jsonmessage variable in the AlertView. if I put the AlertView in DO I get this error: "libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)"
Sorry for my bad English. This is my code:
request.httpBody = postParameters.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = myJSON {
var jsonmessage : String!
jsonmessage = parseJSON["message"] as! String?
print(jsonmessage)
}
} catch {
}
}
task.resume()
let alert = UIAlertController(title: "Alert", message: jsonmessage /*not accessible*/ , preferredStyle: .alert)
alert.addAction(UIAlertAction(title:"Ok", style:UIAlertActionStyle.default, handler:{ (UIAlertAction) in
_ = self.navigationController?.popToRootViewController(animated: true)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}))
self.present(alert, animated: true, completion: nil)
As you have discovered jsonMessage is not accessible from where you are trying to access it.
This is because of a few reasons:
The request is an asynchronous task that runs in the background and takes some time to complete. So the alert view code actually runs before the jsonMessage is returned
The variable jsonMessage is also out of scope where you are trying to call it.
To help explain:
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
let fakeMessage = "hi"
// data, response, error and fakeMessage only exist here upto the closing bracket.
}
task.resume()
// fakeMessage doesn't exist here at all.
To resolve your issue you can either present your alert from within the closure (where I have put fakeMessage) or you se a completionHandler to return jsonMessage when it is ready and then show the alert.
Method 1
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = myJSON {
var jsonmessage : String!
jsonmessage = parseJSON["message"] as! String?
DispatchQueue.main.async {
// some helper function to show a basic alert
self.presentAlert(title: "Response", message: jsonMessage)
}
}
} catch {
}
}
task.resume()
Method 2
func fetchSomething(completion: #escaping (String -> Void)?) {
// setup request
let task = URLSession.shared.dataTask(with:request as URLRequest){
data, response, error in
let fakeMessage = "hi"
completion(fakeMessage)
}
task.resume()
}
then you can use it this way
self.fetchSomething { response in
DispatchQueue.main.async {
// some helper function to show a basic alert
self.presentAlert(title: "Response", message: jsonMessage)
}
}
First of all, you are doing a request that's asynchronous. The jsonmessage variable is set once you get the response. But you create the UIAlertController before this happens. I'm guessing you wish to display the alert once you get a response?
Also, you can't access the jsonmessage variable outside of its scope. To fix this, move var jsonmessage : String! so that it belongs to the same scope as the UIAlertController.
You should be able to move your alert into the do catch statement, but you have to make sure that the alert is displayed on the main thread.
Related
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'm new to Swift and while making one of the tutorials (fairly old) which involves getting credentials from a server through php which returns a JSON, but I'm stuck with the error Ambiguous reference to member jsonObject(with:options:) in the json var, I've searched and trying applying the different solutions but to no avail. :(
Thank you for your time and help.
here is my code:
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
if((userEmail?.isEmpty)! || (userPassword?.isEmpty)!) {
displayMyAlertMessage(userMessage: "All Fields are required.")
return;
}
let myUrl = URL(string: "/UserLogin.php");
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = "email\(userEmail)&password=\(userPassword)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, URLResponse, error in
if error != nil {
//print = ("error=\(error)");
return
}
var err: Error?
var json = JSONSerialization.jsonObject(with: data, options: .mutableContainers, error: &err) as? NSDictionary
if let parseJSON = json {
var resultValue:String = parseJSON["status"] as String!;
print("result: \(resultValue)")
if(resultValue == "Success") {
//Login Succesful
UserDefaults.standard.set(true, forKey:"isUserLoggedIn");
UserDefaults.standard.synchronize();
self.dismiss(animated: true, completion: nil);
}
}
}
task.resume()
There are two major issues:
The actual error occurs because the response parameter in the completion block is wrong. Rather than the type URLResponse it must be a parameter label / variable.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
Since you are using Swift 3 there is no error parameter in jsonObject(with. The method does throw, you need a do - catch block. And – as always – the option .mutableContainers is completely useless in Swift. Omit the parameter.
do {
if let parseJSON = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let resultValue = parseJSON["status"] as? String {
print("result: ", resultValue)
if resultValue == "Success" {
//Login Succesful
UserDefaults.standard.set(true, forKey:"isUserLoggedIn")
self.dismiss(animated: true, completion: nil)
}
}
} catch {
print(error)
}
Some other notes:
To check the text fields safely use optional binding
guard let userEmail = userEmailTextField.text, !userEmail.isEmpty, let userPassword = userPasswordTextField.text, !userPassword.isEmpty else {
displayMyAlertMessage(userMessage: "All Fields are required.")
return
}
Declare Swift constants always as let (for example resultValue)
Do not use NSArray / NSDictionary in Swift. Use native types.
Do not use parentheses around if conditions and trailing semicolons. They are not needed in Swift.
UserDefaults.standard.synchronize() is not needed either.
String.Encoding.utf8 can be reduced to just .utf8.
I am trying to populate a UITable with a json result. I call a function that gets the json from the server and stores the result in NSDictionary. I want to be able to use this collection, and then populate a table. I run into a problem however because for the func numberOfRowsInSection I need the count of the collection, and since my json result is within another function inside a try/catch I cant seem to return the value.
This is what I have for the function which I call in ViewDidLoad():
func getSubjects() -> NSDictionary{
let myUrl = NSURL(string: "www.mydomain.com/script.php");
let request = NSMutableURLRequest(url:myUrl as! URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print("error=\(error)")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue: NSDictionary = parseJSON["subjects"] as! NSDictionary
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
If I print resultValue I get what I need, in this example being:
{
1 = (
Maths,
Lecture
);
2 = (
Science,
Lecture
);
3 = (
English,
Seminar
);
}
But the confusion is, how do I go about returning this value? and where? and how would I implement it in the table? If I try to return resultValue when I parse the JSON I get the error that it is unexpected non-void return in void function and if I try to return the value at the end of the function, I get an unresolved identifier error
I feel I am implementing this incorrectly. I have checked many tutorials on this, and no one seems to populate a table with a POST JSON so I don't know how to go about returning the value, or the proper method of implementation. Any help would be greatly appreciated!
The problem your having is that the dictionary hasn't been retrieved by the time you try to return it. You can use an asynchronous callback to get the dictionary after it has been retrieved from your database.
func getSubjects(callback: #escaping (NSDictionary)-> Void){
let myUrl = NSURL(string: "www.mydomain.com/script.php");
let request = NSMutableURLRequest(url:myUrl as! URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print("error=\(error)")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue: NSDictionary = parseJSON["subjects"] as! NSDictionary
callback(resultValue)
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
and then you would call this function like...
getSubjects(callback: {(resultValue)-> Void in
print(resultValue)
//here you could set your tableView data equal to the values in the dictionary that was just received and then call self.tableView.reloadData to update your table view
})
So perhaps in the viewDidLoad() function of you UITableViewController or in the viewDidAppear(_:), depending on the life cycle of your view, you would call getSubjects(...) as i have shown above and then when the callback is called you call self.tableView.reloadData() as I have explained in the function call. If you are unsure how to setup a tableview datasource and delegate then you should probably open another question for that
EDIT
In response to your comment asking how to use the retrieved value from your server as a variable available to your whole class, you could do something like this...
class: ExampleViewController {
var resultsDictionary: [Int: [String: String]]?
override func viewDidLoad(){
getSubjects(callback: {(resultValue)-> Void in
resultsDictionary = resultValue
})
}
//Use the actual getSubjects function I have already shown you above
func getSubjects(){...}
}
I am currently trying to download, parse and print JSON from an URL.
So far I got to this point:
1) A class (JSONImport.swift), which handles my import:
var data = NSMutableData();
let url = NSURL(string:"http://headers.jsontest.com");
var session = NSURLSession.sharedSession();
var jsonError:NSError?;
var response : NSURLResponse?;
func startConnection(){
let task:NSURLSessionDataTask = session.dataTaskWithURL(url!, completionHandler:apiHandler)
task.resume();
self.apiHandler(data,response: response,error: jsonError);
}
func apiHandler(data:NSData?, response:NSURLResponse?, error:NSError?)
{
do{
let jsonData : NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary;
print(jsonData);
}
catch{
print("API error: \(error)");
}
}
My problem is, that the data in
do{
let jsonData : NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary;
print(jsonData);
}
remains empty.
When I debug,the connection starts successfully, with the given url as a parameter. But my jsonData variable doesn't get printed. Instead the catch block throws the error, stating that there is no data in my variable:
API error: Error Domain=NSCocoaErrorDomain Code=3840 "No value."
Can someone please help me with this?
What am I missing?
Thank you all very much in advance!
[Edited after switching from NSURL Connection to NSURLSession]
Here's an example on how to use NSURLSession with a very convenient "completion handler".
This function contains the network call and has the "completion handler" (a callback for when the data will be available):
func getDataFrom(urlString: String, completion: (data: NSData)->()) {
if let url = NSURL(string: urlString) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { (data, response, error) in
// print(response)
if let data = data {
completion(data: data)
} else {
print(error?.localizedDescription)
}
}
task.resume()
} else {
// URL is invalid
}
}
You can use it like this, inside a new function, with a "trailing closure":
func apiManager() {
getDataFrom("http://headers.jsontest.com") { (data) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let jsonDict = json as? NSDictionary {
print(jsonDict)
} else {
// JSON data wasn't a dictionary
}
}
catch let error as NSError {
print("API error: \(error.debugDescription)")
}
}
}
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()