How does NSURLSession exactly work? - ios

The following lines of code are written inside a function and session_id and session_name are global variables.
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("dataString1 = \(dataString)")
var json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary
if(error != nil) {
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
print(error)
}
else
if let parseJSON = json {
print("jsonstr = \(json)")
if let success = parseJSON["success"] as? String {
if success == "true" {
let session_valid = parseJSON["session_valid"] as! String
if session_valid == "true"{
let response = parseJSON.objectForKey("response") as! NSDictionary
print("response = \(response)")
session_id = response.objectForKey("session_id") as! String
session_name = response.objectForKey("session_name") as! String
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let resultController = self.storyboard!.instantiateViewControllerWithIdentifier("splitViewController") as? UISplitViewController {
self.presentViewController(resultController, animated: true, completion: nil)
}
})
}
}
}
print("values1 are \(session_id) and \(session_name)") //print stmt 1
return
}//if
else {
print("json not parsed")
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: \(jsonStr)")
print(error)
}//else
}
print("values are \(session_id) and \(session_name)") //print stmt 2
task.resume()
The output relevant to the query is: (session_id and session_name are initialised to random values 1 and a)
values are 1 and a
and then it prints: (values of the two variables in the response from the php script)
values1 are ckj0uuj2q18m97m78m1uvje7f5 and d72d1363f44031cac4148b0e6fa295d6
My query is that how is 'print stmt 2' printed before 'print stmt 1'? I am new to swift. Am I missing any concept here?
Also, why does 'print stmt 2' print the initial values of the variables and not the new values? I know the two questions are related but I am not getting how

The code inside that big block (Swift calls it a closure) gets run after the request completes. There's not a way to get a value over the Internet immediately, because it can take up to 90 seconds, and you don't want your entire app blocked for that long (particularly because iOS will kill your app outright after 30).
The way you solve that is by changing the code that needs the values so that it provides code to run after the values have been retrieved, to do something with those values.
See https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html for more info.

Related

How to parse JSON when there is no key but only an Integer / String value?

How can I parse this JSON file ? My code is working when both keys and values are available .
My code so far :
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")
let task = URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
print("Task Started")
if error != nil {
print("In Error!")
} else {
if let content = data {
do {
let myJSON =
try JSONSerialization.jsonObject(with: content, options: .mutableContainers) as AnyObject
print(myJSON)
} catch {
print("In Catch!")
}
}
}
})
task.resume()
print("Finished")
If the root object of the JSON is not a dictionary or array you have to pass .allowFragments as option (btw. never pass .mutableContainers, it's meaningless in Swift)
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("Task Started")
guard error == nil else {
print("In Error!", error!)
return
}
do {
if let myJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? Int {
print(myJSON)
}
} catch {
print("In Catch!", error)
}
}
task.resume()
print("Finished")
THIS ANSWER IS NOT CORRECT. IT IS POSSIBLE TO PARSE Int , etc like in vadian post
This is not a json object format specification.
JSON data must start with "{" for object
or "[" for array of elements.
http://www.json.org/
So, if you have got different formats I would suggest this:
Check the first letter. if "{" parse as object.
Check the first letter. if "[" parse as array.
Otherwise:
Just convert the String into Int something like this:
var num = Int("339")
If not use simple String.

Can't get specified value from JSON in Swift 3

a quick explanation for what I'm doing, there is a login screen user types an email, then there is a verification viewController pops up, the backend sends a verification code to the user's email, if the user's email + the verification code matches then he is logged in successfully and the backend respond with user's full info + unique ID. So I want to get that unique ID alone.
My Verification viewController code:
#IBAction func doneBtnPressed(_ sender: Any) {
let request = NSMutableURLRequest(url: NSURL(string: "http://anydomain.com/verif.php")! as URL)
request.httpMethod = "POST"
let postString = "bdemail=\(bdEmail)&verifcode=\(verifyCodeTxtField.text!)"
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
}else {
do {
if let data = data,
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let users = json["BD_Id"] as? [[String: Any]] {
print(users)
}
} catch {
print("Error deserializing JSON: \(error)")
}
}
print("response = \(response)")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(responseString)")
}
task.resume()
self.performSegue(withIdentifier: "mySegueIdentifier", sender: nil)
}
But I only get the full response, I want the to get the Unique ID --> "BD_Id" alone so I can store it to use it in my app.
Here is the response:
{ URL: http://anydomain.com/verif.php } { status code: 200, headers {
Connection = "Keep-Alive";
"Content-Type" = "text/html; charset=UTF-8";
Date = "Tue, 07 Feb 2017 07:40:00 GMT";
Server = Apache;
"Transfer-Encoding" = Identity;
} })
responseString = Optional( [{"Rcode":101,"BD_Id":"8","BD_Name":"","BD_Email":"email#domain.com","BD_Country":"","BD_Home_Lat":"","BD_Home_Lon":"","BD_BT":"","BD_RH":"","BD_DOB":"0000-00-00","BD_Mobile":"","BD_Last_Donation":"0000-00-00","BD_Token":""}])
The ID did't print out, so what is my mistake ?
Thanks in advance. And please note that i'm very beginner in Swift Networking and thats my first time to deal with JSON.
UPDATE 1:
I have tried the following code but still didn't work, and the print statement didn't come out, that means that the data is empty||nil, but if that so how the last print statement prints the whole JSON script !
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}else {
do {
if data != nil {
print("Hello from the inside...")
let json = try JSONSerialization.jsonObject(with: data!) as? [[String: Any]]
for obj in json! {
if let bdId = obj["BD_Id"] as? String {
print(bdId)
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
}
print("response = \(response)")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(responseString)")
}
Try this ... your JSON is Array of Dictionaries .. [[String:Any]]
do{
let json = try JSONSerialization.jsonObject(with: data!, options:[]) as! [[String:Any]]
for obj in json {
if let bdId = obj["BD_Id"] as? String {
print(bdId)
}
}
}
catch {
print("Error with Json: \(error)")
}

Swift How to update (modify, Delete, Add) entries of JSON

Hello i need some help here, I'm making an IOS app that gets data from a JSON API and then showing the results on a Table , when i tap on a result from the table it goes to a second view controller where i'm showing the details. What I want to do is to update the info I'm showing on the details, delete entries from the JSON by deleting them from the table itself, and add a new entry to be saved on the JSON.
This is the JSON structure:
{
_id: "57eec6c9dfc2fb03005c0dd0",
ssid: "nonummy",
password: "accumsan",
lat: 29.39293,
lon: 115.71771,
summary: "curae nulla dapibus dolor vel est donec odio justo sollicitudin ut",
__v: 0,
likes: 1,
unlikes: 0,
bssid: "EF:CD:AB:56:34:12"
},
I want to be able to update the SSID, Password and Summary.
this is the code I'm using to get the Result from the JSON and is working good
Code:
let url = URL(string:"https://fierce-peak-97303.herokuapp.com/api/wifi")!
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)
//print(jsonResult))
for item in(jsonResult as? NSArray)! {
let ssid = (item as? NSDictionary)?["ssid"] as? NSString
//print(ssid)
}
self.tableData = jsonResult as! NSArray
DispatchQueue.main.sync(execute: {
self.table.reloadData()
})
}catch {
print("No Json Result Was Found")
}
}
}
}
task.resume()
For example if I click on one line of the table I want to be able to update password.
I managed to do it like this : all formatted for swift 3
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["ssid": newSSID.text!,"password": newPass.text!,"lat": newLat.text!,"lon": newLon.text!,"summary": newSum.text!] as Dictionary<String, String>
//create the url with NSURL
let url = URL(string: "https://fierce-peak-97303.herokuapp.com/api/wifi")
//create the session object
let session = URLSession.shared
//now create the NSMutableRequest object using the url object
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
var err : NSError?
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
}catch{
print("error")
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
let strData = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Body: \(strData)")
var err: NSError?
do {
var json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
}catch{
print("JSON error")
}
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
if(err != nil) {
print(err!.localizedDescription)
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: '\(jsonStr)'")
}
else {
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
let success = parseJSON["success"] as? Int
print("Success: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: \(jsonStr)")
}
}catch{
print("JSON error")
}
}
You can get the contents of the json file as whatever data structure you want, then overwrite the values that you want to change and finally overwrite the original file when you want to save your changes:
So you're already getting the JSON as an array:
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
let array = (jsonResult as? NSArray)!
So now you can just change the values in array, when you're done changing everything you want then you can overwrite the json file: (swift 3.0)
var jsonData: NSData!
// serialize json dataa
do
{
jsonData = try JSONSerialization.dataWithJSONObject(array, options: NSJSONWritingOptions())
let jsonString = String(data: jsonData as Data, encoding: String.Encoding.utf8)
print(jsonString)
}
catch let error as NSError
{
print("Array to JSON conversion failed: \(error.localizedDescription)")
}
// overwrite the contents of the original file.
do
{
let file = try FileHandle(forWritingToURL: url!)
file.writeData(jsonData)
print("JSON data was written to the file successfully!")
}
catch let error as NSError
{
print("Couldn't write to file: \(error.localizedDescription)")
}
I'm not sure exactly how writing to a server works, but since it's a URL i think it should act the same way.
Update:
So your original array is:
let array = (jsonResult as? NSArray)!
You can cast it to an array of dictionaries like so
var dictionaries : [[String: NSObject]] = array as? [[String: NSObject]]
and you get ssid by doing:
int i = 0;
for (dictionary in dictionaries)
{
let ssid = dictionary["ssid"]
let newSSID = ssid + "something I want to edit my values with"
// now we have our new value, so we update the array
dictionaries[i]["ssid"] = newSSID
i += 1
}
now after this we just overwrite the original file with the new dictionaries object, which I wrote out above.

iOS - calling Webservice and parsing JSON in Swift

I am using NSURLSession to call my own Webservice which returns JSON, works fine with this code:
func getJSONFromDatabase(){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
self.json = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(self.json)
}
task.resume()
}
However, it seems that this Task is not executed in the right order, because when i run the following function after the "getJSONFromDatabase" function, the "print" statement in the Task is executed after the "print" statement from the "parseJSON" function.
func parseJSON(){
let data = self.json.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! NSArray
for event in json {
let name = event["name"]
let startDate = event["startDate"]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.dateFromString(startDate as! String)
if date != nil {
self.events.append(Event(name: name as! String, startDate: date!))
}
else {
print("Date is nil")
}
}
for event in self.events {
print(event.name)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
My goal is to save the JSON Data in an Object Array of "Event", but this doesn't seem to work because when i iterate over my "self.events" array, it is empty.
Another problem is: When i split this 2 things like i posted here (2 functions), the "parseJSON" throws an error:
Failed to load: The data couldn’t be read because it isn’t in the correct format.
However, if i add the content of "parseJSON" into the Task of the "getJSONFromDatabase" function, there is no such error, but the array is still empty
dataTaskWithURL is asynchronous so you your code won't execute from the top down. Use a completion handler to work on the result from the asynchronous call.
func getJSONFromDatabase(success: ((json: String)->())){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let json = NSString(data: data!, encoding: NSUTF8StringEncoding)
success(json: json)
}
task.resume()
}
in use
getJSONFromDatabase(success: {json in
//do stuff with json
})

I am having trouble parsing JSON that is returned from the NSURL session. What am I doing wrong?

I'm successfully making a POST request and getting data back in return. I can then print that statement in the form of an NSString, after converting of course.
However, I want to save the returned json in usable variables/constants so I can display in the next subsequent screens. I'm having trouble getting various JSON parsing code to work.
I think some of my weaknesses is not understanding the form of 'data', that is returned when I use the NSURLSession. I used code I found elsewhere, and don't quite grasp what the return types are. Is that data from the code below in JSON? NSData?
Anyways, this script works until I start parsing through the data, trying to make sense of how to abstract the array.
If it helps my console returns my print statements (after converting to a NSString) as such:
Optional({
"result": {
"girl": "monica",
"waist": 22.0,
"hair": "Brunette",
"location": "Los Angeles"
}
})
When I try and use the NSJSON serialization framework, the output of that object looks like this, which is supposedly an NSDictionary?:
{
result: {
girl: monica;
waist: 22.0;
hair: Brunette;
location: "Los Angeles";
}
}
A few things confuse me. Why did the quotation marks go away, and what is the optional and extra "result" attribute... Here's my code starting from NSURL request to end of code
let request = NSMutableURLRequest(URL: NSURL(string: "http://localhost:5000")!)
request.HTTPMethod = "POST"
let postString = "color=\(finalDataPassed)&type=\(thirdDataPassed)&hair=\(dataPassed)&location=\(secondDataPassed)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
}
println("response = \(response)")
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println(responseString)
var error: NSError?
let result = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error)
as? NSDictionary
if(error != nil) {
println(error!.localizedDescription)
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Error could not parse JSON: '\(jsonStr)'")
}
else {
if let parseJSON = result {
println(parseJSON)
for item in parseJSON { // loop through data items
let obj = (item as! NSDictionary).objectForKey("result") as! NSDictionary
for (key, value) in obj {
println("Property: \"\(key as! String)\"")
}
}
}
else {
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Error could not parse JSON: \(jsonStr)")
}
}
Optional (they ? in code) means that the it can be nil. Check Swift Basics
The quotes went away because it's a printout of an NSDictionary which does not print out in a JSON format.
Note: To make things safer since you never know what server can return:
if let dataArray = result["result"] as NSArray{
//your code
}
You have an dictionary inside a dictionary:
{
result: <- Key first dictionary
{
girl: monica; <- start of second dictionary
waist: 22.0;
hair: Brunette;
location: "Los Angeles"; <- end of second dictionary
}
}
For a generic approach you can use:
for item in dataArray { // loop through data items
let obj = item.first as! NSDictionary
for (key, value) in obj {
println("Property: \"\(key as! String)\"")
for (key2, value2) in (value as NSDictionary){
println("Property: \"\(key2 as! String2)\"")
}
}
}
In your case because the first dictionary is just one key you can retrieve it directly instead use the first for loop:
for item in dataArray { // loop through data items
let obj = (item.first as! NSDictionary).objectForKey("result") as NSDictionary
for (key, value) in obj {
println("Property: \"\(key as! String)\"")
}
}

Resources