Clousure code won't execute wrapped in a function - swift

I have a closure making a http call wrapped in a function which is called from the click of a button. However when I debug I can see the code within the closure never executes, the programme jumps out of the function altogether when it reaches the closure.
func getTheForeCast(city: String) {
println("Function getForecast city passed = : \(city)")
var webAddress: String = "http://www.weather-forecast.com/locations/\(city)/forecasts/latest"
println("Web address url : \(webAddress)")
let url = NSURL(string: webAddress)
println(url!)
// PROGRAM EXITS FUNCTION HERE
let openbrowserSession = NSURLSession.sharedSession().dataTaskWithURL(url!) {
(data, response, error) in
// in the following code, session returns data, error, and response
println("In closure")
if error == nil {
// no errors, convert html to readable data
var urlConverted = NSString(data: data, encoding: NSUTF8StringEncoding)
println(urlConverted)
// run this asynchronously using a grand central dispatch
dispatch_async(dispatch_get_main_queue()) { self.webview_displayWeather.loadHTMLString(urlConverted, baseURL: nil) } // dispatch
} else if error != nil {
println("Error loading page")
println(error.description)
}
} // closure
} // func
Any input appreciated.

The tasks created by NSURLSession are initially in the "suspended" state.
You have to call resume() after creating the task:
let openbrowserSession = NSURLSession.sharedSession().dataTaskWithURL(url!) {
(data, response, error) in
// ...
}
openbrowserSession.resume()
otherwise nothing will happen.

You use the wrong signature. Use
func dataTaskWithURL(_ url: NSURL,
completionHandler completionHandler: ((NSData!,
NSURLResponse!,
NSError!) -> Void)?) -> NSURLSessionDataTask

Related

Sync don't goes in sync and can't fill array out DispatchQueue

I'm developing some code for my App over macOS using DispatchQueue in sync
but don't know how to use it correctly.
There's a function that have to print out in order:
response, A
public func a (_ sender: AnyObject) {
var response: [String] = [ ]
DispatchQueue.global().sync { // Correct
requestUrlFunct(urlString: "https://hostname.com") { (a) in a.enumerateLines { line, _ in
if( line != "" ){
response.append(line)
////EDIT: here i have to populate a popup. Someting like popup?.addItem(withTitle: line) and if it's not syncronous I've got a core error
}
}
print(response)
}
}
print("A")
//here something like self.popup.isEnabled = true;
}
this function calls:
public func requestUrlFunct(urlString: String, completionBlock: #escaping ([Any]) -> Void) -> Void {
let requestURL = URL(string: urlString)
var request = URLRequest(url: requestURL!)
var response: [String] = [ ]
let requestTask = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
let a = String(data: data!, encoding: String.Encoding.utf8) as String!
completionBlock([a!]);
}
DispatchQueue.global().sync {
requestTask.resume()
}
}
but at the end the result is:
A, response
Also how to fill var response: [String] = [ ] that's out of DispatchQueue? Can't fill it.
Wrapping your URL calls in DispatchQueue.global().sync won't make the URL call actually behave synchronously -- the URL call itself will still be an asynchronous call. Which, if you think about it, is a very good thing -- we don't want our programs freezing up, if, for example, our network connection isn't responsive.
In your current code, func a runs, and then calls out to requestUrlFunct, which includes asynchronous code. That async code doesn't return automatically, so a keeps running and gets to your print("A") line. Then, the URL call returns (asynchronously) and calls your callback function (which you're passing via a trailing closure to requestUrlFunct). At that point, your print(response) line is called.
You're already making use of the concept that you'll need to use, which is "callback functions." With a callback function, you can execute some code once you get a response. This may mean you have to restructure some of your code, because you won't be able to count on an immediate synchronous response. For example, you won't be able to return your response from your func a.
The good news is that this will clean up some of the code you have considerably. You might notice that I use guard statements and as? to avoid force unwrapping with ! that can cause crashes.
public func a (_ sender: AnyObject) {
requestUrlFunct(urlString: "https://hostname.com") { responseArray in
DispatchQueue.main.async {
//do something with the response
}
}
//can't access the response here, since it is asynchronous
}
public func requestUrlFunct(urlString: String, completionBlock: #escaping ([String]) -> Void) -> Void {
guard let requestURL = URL(string: urlString) else {
//handle error
return
}
let request = URLRequest(url: requestURL)
let requestTask = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if let data = data, let a = String(data: data, encoding: String.Encoding.utf8) {
completionBlock([a]);
}
}
requestTask.resume()
}
I left one artifact from the original that I'm not really sure about -- the fact that the requestUrlFunct callback returns a [String], but it's only ever going to return one item (since the response gets turned into an array by doing [a]) -- this is something you might want to be looking at when you do some refactoring.
Also worth noting that most of the time with your callback functions, you also want some sort of callback to happen if there's an error. Right now, there's no error handling at all.

Asynchronous thread in Swift - How to handle?

I am trying to recover a data set from a URL (after parsing a JSON through the parseJSON function which works correctly - I'm not attaching it in the snippet below).
The outcome returns nil - I believe it's because the closure in retrieveData function is processed asynchronously. I can't manage to have the outcome saved into targetData.
Thanks in advance for your help.
class MyClass {
var targetData:Download?
func triggerEvaluation() {
retrieveData(url: "myurl.com") { downloadedData in
self.targetData = downloadedData
}
print(targetData) // <---- Here is where I get "nil"!
}
func retrieveData(url: String, completion: #escaping (Download) -> ()) {
let myURL = URL(url)!
let mySession = URLSession(configuration: .default)
let task = mySession.dataTask(with: myURL) { [self] (data, response, error) in
if error == nil {
if let fetchedData = data {
let safeData = parseJSON(data: fetchedData)
completion(safeData)
}
} else {
//
}
}
task.resume()
}
}
Yes, it’s nil because retrieveData runs asynchronously, i.e. the data hasn’t been retrieved by the time you hit the print statement. Move the print statement (and, presumably, all of the updating of your UI) inside the closure, right where you set self.targetData).
E.g.
func retrieveData(from urlString: String, completion: #escaping (Result<Download, Error>) -> Void) {
let url = URL(urlString)!
let mySession = URLSession.shared
let task = mySession.dataTask(with: url) { [self] data, response, error in
guard
let responseData = data,
error == nil,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkError.unknown(response, data))
}
return
}
let safeData = parseJSON(data: responseData)
DispatchQueue.main.async {
completion(.success(safeData))
}
}
task.resume()
}
Where
enum NetworkError: Error {
case unknown(URLResponse?, Data?)
}
Then the caller would:
func triggerEvaluation() {
retrieveData(from: "https://myurl.com") { result in
switch result {
case .failure(let error):
print(error)
// handle error here
case .success(let download):
self.targetData = download
// update the UI here
print(download)
}
}
// but not here
}
A few unrelated observations:
You don't want to create a new URLSession for every request. Create only one and use it for all requests, or just use shared like I did above.
Make sure every path of execution in retrieveData calls the closure. It might not be critical yet, but when we write asynchronous code, we always want to make sure that we call the closure.
To detect errors, I'd suggest the Result pattern, shown above, where it is .success or .failure, but either way you know the closure will be called.
Make sure that model updates and UI updates happen on the main queue. Often, we would have retrieveData dispatch the calling of the closure to the main queue, that way the caller is not encumbered with that. (E.g. this is what libraries like Alamofire do.)

Continue with code after URLSession.shared.uploadTask is completed

I am trying to communicate with Swift to a php-website using the command "uploadTask". The site is sending Data back, which is working well. The result from the website is stored in the variable "answer". But how can I actually use "answer" AFTER the uploadTask.resume() was done?
When running the file, it always prints:
"One" then "three" then "two".
I know that I could do things with "answer" right where the section "print("two")" is. And at many examples right there the command "DispatchQueue.main.async { ... }" is used. But I explicitly want to finish the uploadTask and then continue with some more calculations.
func contactPHP() {
print("One")
let url = "http://....php" // website to contact
let dataString = "password=12345" // starting POST
let urlNS = NSURL(string: url)
var request = URLRequest(url: urlNS! as URL)
request.httpMethod = "POST"
let dataD = dataString.data(using: .utf8) // convert to utf8 string
URLSession.shared.uploadTask(with: request, from: dataD)
{
(data, response, error) in
if error != nil {
print(error.debugDescription)
} else {
let answer = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)!
print("Two")
}
}.resume() // Starting the dataTask
print("Three")
// Do anything here with "answer"
}
extension NSMutableData {
func appendString(string: String) {
let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
append(data!)
}
}
I already tried it with a completion handler. But this does not work either. This also gives me "One", "Four", "Two", "Three"
func test(request: URLRequest, dataD: Data?, completion: #escaping (NSString) -> ()) {
URLSession.shared.uploadTask(with: request, from: dataD)
{
(data, response, error) in
if error != nil {
print(error.debugDescription)
} else {
let answer = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)!
print("Two")
completion(answer)
}
}.resume() // Starting the dataTask
}
let blubb = test(request: request, dataD: dataD) { (data) in
print("Three")
}
print("Four")
Use the URLSession function that has the completion handler:
URLSession.shared.uploadTask(with: URLRequest, from: Data?, completionHandler: (Data?, URLResponse?, Error?) -> Void)
Replace your uploadTask function with something like this:
URLSession.shared.uploadTask(with: request, from: dataD) { (data, response, error) in
if let error = error {
// Error
}
// Do something after the upload task is complete
}
Apple Documentation
After you create the task, you must start it by calling its resume()
method. If the request completes successfully, the data parameter of
the completion handler block contains the resource data, and the error
parameter is nil.
If the request fails, the data parameter is nil and
the error parameter contain information about the failure. If a
response from the server is received, regardless of whether the
request completes successfully or fails, the response parameter
contains that information.
When the upload task is complete, the completion handler of the function is called. You could also implement the delegate's optional func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) function.

swift - order of functions - which code runs when?

I have an issue with my code and I think it could be related to the order in which code is called.
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
private var tasks = [Task]()
override func willActivate() {
let taskUrl = "http://myjsonurl.com"
downloadJsonTask(url: taskUrl)
print(tasks.count) // EMPTY
super.willActivate()
}
func downloadJsonTask(url: String) {
var request = URLRequest(url: URL(string: url)!)
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
URLSession.shared.dataTask(with: request) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
return
}
do
{
let decoder = JSONDecoder()
let downloadedTasks = try decoder.decode(Tasks.self, from: data)
self.tasks = downloadedTasks.tasks
print(downloadedTasks.tasks.count) //4
} catch {
print("somehting went wrong after downloading")
}
}.resume()
}
}
I'm defining the private var tasks and fill it with the downloadJsonTask function but after the function ran the print(tasks.count) gives 0.
When I call print(downloadedTasks.tasks.count) it gives 4.
I think that in sequence of time the tasks variable is empty when I print it and it is filled later on.
When you are trying to print number of tasks in willActivate(), function downloadJsonTask(url: String) hasn't been completed yet, so you have empty array because tasks haven't been set yet.
You should add completion handler to downloadJsonTask just like this:
(don't forget to pass completion as parameter of function)
func downloadJsonTask(url: String, completion: #escaping () -> Void) {
var request = URLRequest(url: URL(string: url)!)
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
URLSession.shared.dataTask(with: request) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
completion()
return
}
do {
let decoder = JSONDecoder()
let downloadedTasks = try decoder.decode(Tasks.self, from: data)
self.tasks = downloadedTasks.tasks
print(downloadedTasks.tasks.count) //4
} catch {
print("something went wrong after downloading")
}
completion() // This is moment when code which you write inside closure get executed
}.resume()
}
In your willActivate() use this function like this:
downloadJsonTask(url: taskUrl) {
print(tasks.count)
}
So that means when you get your data, your code inside curly braces will get executed.
You’re correct in your assumption that tasks has not yet been assigned a value when it’s first printed.
The thing is network requests are performed asynchronously. It means that iOS does not wait until downloadJsonTask(url:) is finished but continues executing the code right away (i.e. it calls print(tasks.count) immediately after the network request started, without waiting for it to produce any results).
The piece of code inside brackets after URLSession.shared.dataTask(with:) is called a completion handler. This code gets executed once the network request is competed (hence the name). The tasks variable is assigned a value only when the request is finished. You can make sure it works by adding print(self.tasks.count) after self.tasks = downloadedTasks.tasks:
self.tasks = downloadedTasks.tasks
print(self.tasks)
print(downloadedTasks.tasks.count)

Return object for a method inside completion block

I want to make a method with URL parameter that returns the response of calling that URL.
How can I return the data obtained inside a completion block for a method?
class func MakeGetRequest(urlString: String) -> (data: NSData, error: NSError)
{
let url = NSURL(string: urlString)
var dataResponse: NSData
var err: NSError
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
//How can I return the data obtained here....
})
task.resume()
}
If you want the MakeGetRequest method to return data obtained via dataTaskWithURL, you can't. That method performs an asynchronous call, which is most likely completed after the MakeGetRequest has already returned - but more generally it cannot be know in a deterministic way.
Usually asynchronous operations are handled via closures - rather than your method returning the data, you pass a closure to it, accepting the parameters which are returned in your version of the code - from the closure invoked at completion of dataTaskWithURL, you call that completion handler closure, providing the proper parameters:
class func MakeGetRequest(urlString: String, completionHandler: (data: NSData, error: NSError) -> Void) -> Void
{
let url = NSURL(string: urlString)
var dataResponse: NSData
var err: NSError
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
completionHandler(data: data, error: error)
})
task.resume()
}
Swift 5 update:
class func makeGetRequest(urlString: String, completionHandler: #escaping (Data?, Error?) -> Void) -> Void {
let url = URL(string: urlString)!
var dataResponse: Data
var err: NSError
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, respone, error) -> Void in
completionHandler(data, error)
})
task.resume()
}