Run JSON Request in the background Swift 4 - ios

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()
}

Related

How can I connect to a hard coded IP address using http in iOS 12+

I am trying to make a simple prototype with both a server (EC2 dev server) and a client. Where the server just has a hard IPv4 address (numbers) and send http results upon requests and the client is a prototype iOS 12+ app where I simply want to retrieve data from the server that is using http. How can I do so? Thanks in advance!
Read up on URLSession and how to fetch webside data into memory.
func startLoad() {
let url = URL(string: "http://IPADDRESS/")!
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
}
// Data is in the data-variable.
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
// Avoid using the main-thread to return any data.
DispatchQueue.main.async {
// Return data to use in view/viewcontroller
self.webView.loadHTMLString(string, baseURL: url)
}
}
}
task.resume()
}
Other things you probably will want to get into is MVVM/MVC for managing data between views and Codables.

How to get Mutable data from an NSURLRequest

I'm trying to retrieve a text file from a URL and then process that text file in the swift Data form. It's like a CSV file but uses "|" for the column delimiter and "}" for the row delimiter.
I'd like to remove the first "line" up to a "}" character (my line delimiter is a "}" so that I can cycle through the file until it's empty.
However NSURLRequest returns an immutable Swift Data object.
I guess I can copy it into a mutable copy but I prefer it if I could persuade NSURLRequest to return a mutable Data object. Is that possible?
My URL request looks like this:
func load(url: String) {
debugPrint(#function)
let url = URL(string: url)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error: error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(error: response)
return
}
debugPrint("data got")
self.dataGot(data: data!)
DispatchQueue.main.async {
self.loadingDelegate!.stockLoadComplete()
}
}
task.resume()
}
currently I'm creating a string from the entire file and doing some string operations to split rows and columns:
let asString = String(data: data, encoding: String.Encoding.utf8)
let rows = asString!.components(separatedBy: "}")
for row in rows {
self.addPriceLine(line: row)
}
This approach is failing with a malloc error (after successfully processing a few hundred rows) so I suspect that I'm going down the wrong road somehow.
Is there a "good" or recommended approach? Using just a Data object seems a lot more elegant to me.
Advice appreciated.
OK so I solved the problem.
I was getting a runtime malloc error which made me think there was some problem with the Data buffer or its conversion to a string.
I wandered if it was something failing with memory allocation in the closure so I put the gotData() processing onto the main dispatch queue. Voila - the malloc went away.
Clearly there are limits to what you should do in the closure, rather than the main dispatch queue and I guess I was misusing the approach.
:)
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error: error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(error: response)
return
}
debugPrint("data got")
DispatchQueue.main.async {
self.dataGot(data: data!)
self.loadingDelegate!.stockLoadComplete()
}
}

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.

JSON parsing and returning data in Swift

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'.

NSURLSession's sharedSession is very slow. Is there a way to speed up dataTaskWithURL?

When I ping the exact same URL in a browser or with a terminal ping the response time is less than 300ms. When I make the request in the app with the code below it takes 3 seconds to get the response data. I even tried it with http://www.google.com and the response time takes 2-3 seconds to complete. Are there any tricks to get quicker responses when requesting data from a URL in Swift? What is going begind the scenes to make the request? Is there some way to warm up the connection before constructing the call?
UPDATE: If I call the function many times in a row it begins to respond very quickly(~200ms). That makes me believe that there is some sort of ramp up time to create the connection - where subsequent calls are lightning fast. It is an https call, but again I know it isn't the server because the ping and browser handle it in milliseconds. Is there some sort of known delay in iOS/Swift when opening an initial request?
func phraseSearch(var text: String, completion: ([Translation])-> Void) {
let urlString = baseAPIString + "/search?q=test"
let session = NSURLSession.sharedSession()
let searchUrl = NSURL(string: urlString)
let task = session.dataTaskWithURL(searchUrl!) {
(data, response, error) -> Void in
if error != nil {
print(error?.localizedDescription)
} else {
let searchData: NSDictionary!
let translationsData: NSArray!
do {
searchData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
} catch _ {
searchData = NSDictionary()
}
translationsData = searchData["translations"] as! NSArray
var translations = [Translation]()
for translation in translationsData {
let translation = Translation(data: translation as! NSDictionary)
translations.append(translation)
}
dispatch_async(dispatch_get_main_queue(), {
completion(translations)
})
}
}
task.resume()
}
UPDATE: I added time stamps to show timing per this screenshot: Screenshot with time stamps

Resources