JSON parsing and returning data in Swift - ios

i have a Swift code for retrieving and parsing JSON data from the web. everything seems to work fine except one problem i am facing right now. I tried to solve it for some time, have check all sources online.
I have created global dictionary "dicOfNeighbours" that would like to return as a result of parse to other class by calling "func startConnection".
dicOfNeighbours stores parsed data till it goes out of the closing bracket of the:
"let task = session.dataTaskWithRequest(urlRequest) { ... }"
After it stores just nil. And returned result is nil as well.
I have tried to pass "dicOfNeighbours" variable by reference using inout and it is still returns nil result.
there might some solution that i missed.
I would appreciate any help and suggestions.
Thanks!
var dicOfNeighbours = Dictionary<String, [Int]>()
func startConnection() -> Dictionary<String, [Int]>{
let requestURL: NSURL = NSURL(string: "http://www....data.json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let neighbours = json["neighbours"] as? [String: Array<Int>] {
var i = 0
for (index, value) in neighbours {
self.dicOfNeighbours[index] = value
}
}
}catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
return self.dicOfNeighbours
}

You are using return instead of using a callback. You are doing your parsing when the network connection is done; asynchronously.
To synchronize it, you'd need to use semaphores, but that is highly discouraged on the main thread.
Instead, do the appropriate things with the result when your completion block is executed. Think of the data task as 'do stuff, come back to me when you're done'.

Related

Run JSON Request in the background Swift 4

I need to run this code in the background if possible. Im getting a JSON Request that sometimes takes a while to load(lag is on the server side of the URL, not the code itself.).
I want to run the code below in the background if possible. Any ideas?
var stockData: Data!
var concatTickersString = ""
for object in dataArray.reversed() {
concatTickersString = concatTickersString + "," + object.symbol
}
let url = URL(string: "https://www.alphavantage.co/query?function=BATCH_STOCK_QUOTES&symbols=" + concatTickersString + "&apikey=IX58FUCXKD695JY0")
do {
stockData = try Data(contentsOf: url!)
let json = try JSON(data: stockData)
if let jsonArray = json["Stock Quotes"].array {
for ticker in jsonArray.reversed() {
if(jsonArray.count != 0){
let stockTicker = ticker["1. symbol"].string!
let stockPrice = ticker["2. price"].string!
self.watchListArray.append(WatchlistData(tickerName: stockTicker, tickerPrice: Double(stockPrice)?.currency))
}
}
tableView.isHidden = false
}
} catch {
print(error)
}
Its the server of the JSON that takes long I dont think its necessarily the Data(contents of)
I tried using dispatch_async but im getting no luck.
The lag is caused by the fact that Data(contentsOf:) is a synchronous method. As the documentation says,
Important
Don't use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.
As you discovered through experimentation, placing this method in DispatchQueue.main.async does not make it asynchronous. Instead, follow the documentation's instruction.
This is the slightly modified example found at Fetching Website Data into Memory:
func startLoad() {
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
if let data = data,
let string = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
doSomething(with: string)
}
}
}
task.resume()
}

how do I array from a GET request function in viewdidload function in swift

I'm very new to swift, so I will probably have a lot of faults in my code but what I'm trying to achieve is send a GET request to a server with paramters inside a function. I want to use the array I receive from the server in my viewdidload and in other functions but cant seem to find a way to store the array so i can use it. in my function it is filled, but out of my function it is empty
var scenarioArray: Array<Any> = []
let idPersoon = UserDefaults.standard.object(forKey: "idPersoon") as! String
override func viewDidLoad() {
super.viewDidLoad()
ScenarioArray()
print(scenarioArray)
print(self.scenarioArray)
}
func ScenarioArray() {
var request = URLRequest(url: URL(string: "http://dtsl.ehb.be/app&web/ios_php/getAllScenariosByPersoon.php?persoonID="+idPersoon)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
}
Your "problem" is that you are trying to GET data from a server, meaning that you are doing a network call.
Now...you don't know how long that network call will take when you launch it, if you are on a good network then it might be fast, but if you are on 3G network it might take a while.
If the call to your server was done synchronously, the result would be that each and every time you'd try to fetch data your code would focus on doing just that, meaning that nothing else would go on... that is not what you want :)
Instead, when you use URLSession, and call task.resume() that method is executed asynchronously, meaning that it starts on another thread in the background where it will fetch data.
In the meantime, your main thread is free to handle UI rendering and so on. At some point in the near future your network call finishes and you now have valid data and must inform whoever needs to know.
So when you do a call to dataTask(with: completionHandler:), what you are actually saying is something along the lines of:
"hey...go fetch this data in the background please, and when you're done, I'd like to execute the code I've passed you here in the completionHandler with the parameters you tell me about".
Hope that makes just a little sense :)
Now...you have this code:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
That last part of the function call ({ data, response, error in...) is the completionHandler, which is not executed straight away. It is not executed until the retrieval of data has completed.
And therefore when you do a call to your ScenarioArray() function in viewDidLoad, what will happen is that the asynchronous call to fetch data will start in the background and your viewDidLoad will continue what it is doing, meaning that when you say:
print(scenarioArray)
print(self.scenarioArray)
then you can not expect scenarioArray to be populated yet as your task is busy fetching that data in the background.
So...what you need to do, as #vadian says, is to update your UI once the data has been fetched, meaning, in the completionHandler.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
//Now you have data, reload the UI with the right scenarioArray
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
Hope that makes sense and helps you.

Data in HTTPBody with a PUT method fails, while it works with a POST?

first of all i would like to say i got the exact same problem as the following question: How to add data to HTTPBody with PUT method in NSURLSession?. But it wasn't answered so i made my own question.
We have written a node API for a school assignment. We've tested the whole API. (The chances of being something wrong there are slim.)
After that i went working on a iOS client to CRUD users.
Making a user is going perfectly, but whenever i try to edit a user something strange happens. The data on the server arrives as undefined.
I use the following code to save a user:
func saveUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "POST"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
keep in mind, this is working perfectly (don't know if it is intended to be used like this)
To edit a user i use the following code:
func editUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/\(user.id!)")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "PUT"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
(The original code is a bit longer but i removed parts that had nothing to do with the actual posting of the data)
I have really no idea what i'm doing wrong, could be something small and stupid. Please help.
edit after input from #fiks
To be clear, the problem i am having is that I fill the "postString" the same way in the editUser method as I do in the saveUser method.(At least I think I do)
However in the saveUser method the postString seems to be correctly passed through to the API (it creates a new user with the given values).
The editUser method does not pass the values through.
If I put a console log on the server it shows all values are "undefined".
To test if the postString was correct on the iOS part I printed both strings out. Both of them outputted email=user#test.com&password=test&role=admin
From what I see in the postman request, you are sending a x-www-form-urlencoded request.
You have to specify it in the code. See example: POST request using application/x-www-form-urlencoded
Regarding Charles: since you are using https, you have to enable proxy for the host. More info here: https://www.charlesproxy.com/documentation/proxying/ssl-proxying/

iOS programming, fetching JSON data

I have added to my project the SwiftyJSON.swift file and I am trying to get some data from the web. Now my project runs but only until the line where I am trying to get the array from json in a dictionary. I cannot understand where the problem is, but I am guessing it has to be something very stupid as I am just in the beginning with learning swift.
I am just trying to print in the console the name of all the movies from that url and after I manage to achieve this performance, I will try to get the summary of the movie as well and then put them in a TableView.
import UIKit
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//grab the status code and check if the transfer was successful == 200
let requestURL: NSURL = NSURL(string: "https://itunes.apple.com/us/rss/topmovies/limit=50/json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
//sort through the stations key and cast the data into an array of dictionaries
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
print("bbbbb")
// From here on, it doesn't print anything anymore
if let movies = json["entry"] as? [[String: AnyObject]] {
print(movies)
print("test")
for movie in movies {
if let name = movie["name"] as? String {
print("mmmm")
print("%# (Built %#)",name)
}
}
}
}catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
entry is an json array, Use .array
if let movies = json["entry"].array {
for movie in movies {
// Do stuff
}
}
Also a general tip. Do not cast the values e.g.
movie["something"] as? String
Rather use the built in features:
movie["something"].string
Update
Looking closer on your code I see that you are acctually not using SwiftyJSON.swift at all.
To use Swifty you parse the json text like this and get a JSON object:
let jsonObj = JSON(data: yourData) // data is a NSData
Please have another look at the documentation:
https://github.com/SwiftyJSON/SwiftyJSON
I think you are reading the section "Why is the typical JSON handling in Swift NOT good?". That section explains the native and "bad" way of managing json in Swift, the real documentation is further down.

Calling methods in swift (Method doesnt finish before next line)

Trying to pull in some JSON data from an API and then save that to core data.
My current method of doing this is to pull in the JSON data and return that array which ill then iterate and save to core data.
Pull in Data: (Works fine)
func getPlayerDataFromAPI() -> [Dictionary<String,AnyObject>]{
let url: String = "http://api.fantasy.nfl.com/v1/players/stats?"
let request : NSMutableURLRequest = NSMutableURLRequest()
var jsonData = [Dictionary<String,AnyObject>]()
request.HTTPMethod = "GET"
request.URL = NSURL(string: url)
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
if (jsonResult != nil) {
if let playerData = jsonResult?["players"] as? [Dictionary<String, AnyObject>]{
jsonData = playerData
print(jsonData.count)
}
} else {
print("No Data")
}
}
catch {
print("Error Occured")
}
}.resume()
return jsonData;
}
And then I wanted to test the returned Dictionary to ensure it was being populated:
func saveData(){
let players = getPlayerDataFromAPI()
print(players.count)
}
I call saveData() in the viewController viewDidLoad method and get an empty dictionary... Moments later, the print statement in the JSON function prints.
0
1427
Is there a reason the getPlayerDataFromAPI() function doesnt finish before the print(count) is being called? Do I have this wrong logically? I always get an empty dictionary returned in this instance and thats no good.
You're trying to synchronously return the results of an asynchronous function. session.dataTaskWithRequest is passed a closure, which doesn't execute until the request completes. So your jsonData = playerData statement doesn't get executed until after your getPlayerDataFromAPI() function has already returned (at which point jsonData is still the empty dictionary you defined at the beginning of the function).
One way to do what you're trying to do is to allow a closure to be passed in to your function; something like this (I haven't tested this code):
func getPlayerDataFromAPI(completion: (data: [String: AnyObject]) -> Void)
Then, at the point you assign jsonData = playerData, you can "return" the data to the caller like this:
completion(data: jsonData)
Calling this function would look something like this:
getPlayerDataFromAPI() { (data) -> Void in
print(data)
}

Resources