Continue with code after URLSession.shared.uploadTask is completed - swift

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.

Related

Cannot convert value of type '()' to expected argument type '(Data?, URLResponse?, Error?) -> Void'

I can't solve this error I hope can I find the solution here
the error is in the line 22 and it's "Cannot convert value of type () to expected argument type (Data?, URLResponse?, Error?) -> Void"
struct WeatherManager {
let weatherURL = ""
func fetchWeather(cityName : String){
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString : String){
//1- Create a URL
if let url = URL(string: urlString){
//2- Create a URLSession
let session = URLSession(configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler:handel(data: <#T##Data?#>, urlSession: <#T##URLSession?#>, error: <#T##Error?#>))
//4- Start the Task
task.resume()
}
}
func handel(data:Data? , urlSession : URLSession? , error : Error?){
if error != nil {
print(error!)
return
}
if let safeData = data{
let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)
}
}
}
here is the error :
let task = session.dataTask(with: url, completionHandler:handel(data: <#T##Data?#>, urlSession: <#T##URLSession?#>, error: <#T##Error?#>))
A few observations:
The signature of “handle” is incorrect. The second parameter is a URLResponse, not a URLSession:
func handle(data: Data?, response: URLResponse?, error: Error?) {
if let error = error {
print(error)
return
}
if let data = data, let string = String(data: data, encoding: .utf8) {
print(string)
}
}
As Leo pointed out, you should avoid force-unwrapping with the ! operator, too (especially when converting the Data to a UTF8 String).
You should not supply parameters when you pass handle to dataTask:
func performRequest(urlString: String) {
//1- Create a URL
guard let url = URL(string: urlString) else { return }
//2- Use existing URLSession
let session = URLSession.shared // (configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler: handle)
//4- Start the Task
task.resume()
}
Unrelated, please note that I have not created a URLSession in performRequest. Sessions will leak memory if you create them but do not invalidate them. In this case, it is easier to use the existing shared session, rather than creating a new one.
If you really needed to create one (which you obviously do not in this case ... use shared instead), you would explicitly tell it to invalidate itself when the request was done.
func performRequest(urlString: String) {
//1- Create a URL
guard let url = URL(string: urlString) else { return }
//2- Create URLSession (but only if absolutely necessary)
let session = URLSession(configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler: handle)
//4- Start the Task
task.resume()
//5- If you really must create a session, invalidate it when the request(s) are finished
session.finishTasksAndInvalidate()
}
But this is an anti-pattern because it is so inefficient. You should reuse a single session, like shown in the previous code snippet, if at all possible.

How to write a completion handler in a separated block of code in Swift with parameters out of scope

I was trying to make my code clean and decouple the code below, I want to remove the trailing completion handler from it and write the completion handler in another blck of code.
func uploadMarcasMetodoNovo(_ id_resenha: Int) {
let resenhaDados:ResDadoModel = db.readDadosResenhaById(id_resenha)
let resenhaMarcas:[ResMarcasModel] = db.readResMarca(id_resenha)
// this for loop runs about for 7 times
for marca in resenhaMarcas {
contadorUploadMarcas = contadorUploadMarcas + 1
myUploadGroupMarcas.enter()
jsonRequestUploadImagemGrafica = ResMarcasModel.createJsonMarcaResenha(marca, resenhaDados.IdGedave )
let json: [String: Any] = jsonRequestUploadImagemGrafica
guard let jsonData = try? JSONSerialization.data(withJSONObject: json) else {
print("guard jsonData error")
return
}
let requestImagemGrafica = requestUploadFotos(jsonData)
let task = URLSession.shared.dataTask(with: requestImagemGrafica) { data, response, error in
if let error = error {
print("error: \(String(describing: error))")
return
}
print("data")
guard let returnData = String(data: data!, encoding: .utf8) else {
print("returnData guard fail")
return
}
print("returnData")
print(returnData)
self.confirmStatusEnviada(marca)
self.myUploadGroupMarcas.leave()
print("end TASK")
}
task.resume()
}
myUploadGroupMarcas.notify(queue: DispatchQueue.main) {
print("myUploadGroupMarcas notify")
// more code ...
}
}
This is the part that I write creating a separated completion handler
let myCompletionHandler: (Data?, URLResponse?, Error?) -> Void = {
(data, response, error) in
if let error = error {
print("error: \(String(describing: error))")
return
}
print("data")
guard let returnData = String(data: data!, encoding: .utf8) else {
print("returnData guard fail")
return
}
self.confirmStatusEnviada(marca)
self.myUploadGroupMarcas.leave()
}
but it won't work because in the last two lines of code are used paramters that are out of scope. The parameter "marca" and the parameter "myUploadGroupMarcas" are out of scope. Is there a way to use these parameters inside the separated completion handler function?
Ok based on our comments above, this is the route I would try: Write a short completion handler that calls your longer completion handler, passing the variables that are out of scope.
let task = URLSession.shared.dataTask(with: requestImagemGrafica) { data, response, error in
myCompletionHandler(data, response, error, marca, myUploadGroupMarcas)
}
Then you add two parameters to your completion handler in the function definition:
let myCompletionHandler: (Data?, URLResponse?, Error?, MarcaClass, myUploadGroupMarcas) -> Void
Obviously you need to replace MarcaClass with the actual class type that is marca and myUploadGroupMarcas seems to be a function so you'd need to write an appropriate parameter type for that.

Making HTTP GET request with Swift 5

I am obviously missing something very fundamental/naïve/etc., but for the life of me I cannot figure out how to make simple GET requests.
I'm trying to make an HTTP GET request with Swift 5. I've looked at these posts/articles: one, two, but I can't get print() statements to show anything. When I use breakpoints to debug, the entire section within the URLSession.shared.dataTask section is skipped.
I am looking at the following code (from the first link, above):
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
HTTP_Request()
I am running this in a MacOS Command Line Project created through XCode.
I would greatly appreciate any help I can get on this, thank you.
Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,
func httpRequest() {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
That should at least give you some indication of the problem.
A few other considerations:
If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.
For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:
func httpRequest(completion: #escaping () -> Void) {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
defer {
DispatchQueue.main.async {
completion()
}
}
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
var finished = false
httpRequest {
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: .distantFuture)
}
In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.
If playground, you have to set needsIndefiniteExecution.
By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.
In this case, you should just use https and avoid that consideration altogether.
Make sure the response get print before exiting the process, you could try to append
RunLoop.main.run()
or
sleep(UINT32_MAX)
in the end to make sure the main thread won't exit. If you want to print the response and exit the process immediately, suggest using DispatchSemaphore:
let semphare = DispatchSemaphore(value: 0)
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
semphare.signal()
}
task.resume()
}
HTTP_Request()
_ = semphare.wait(timeout: .distantFuture)
This works for me many times I suggest you snippet for future uses!
let url = URL(string: "https://google.com")
let task = URLSession.shared.dataTask(with: ((url ?? URL(string: "https://google.com"))!)) { [self] (data, response, error) in
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: [])
print(jsonResponse)
guard let newValue = jsonResponse as? [String:Any] else {
print("invalid format")
}
}
catch let error {
print("Error: \(error)")
}
task.resume()
}

How to asynchronously download an array of URLs as strings

My goal is to download an array of URLs (for example, 25 URLs) as strings, and report when the URLs have all finished downloading.
At the moment, I am able to successfully download from 1 webpage, as configured below:
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://www.apple.com")
self.downloadWebpage(url: url!)
}
func getDataFromUrl(url: URL, completion: #escaping (_ data: Data?, _ response: URLResponse?, _ error: Error?) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
completion(data, response, error)
if error != nil {
print (error.debugDescription)
}
}.resume()
}
func downloadWebpage(url: URL) {
getDataFromUrl(url: url) { (data, response, error) in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? url.lastPathComponent)
print("Download Finished")
print (data)
DispatchQueue.main.async() { () -> Void in
if let returnData = String(data: data, encoding: .utf8) {
print (returnData)
}
}
}
}
While I realize I could easily package my URLs inside an array, my difficulty is about getting the process to report that when all of the URLs from the array are downloaded (or, have error'ed out). Any assistance is appreciated.
Quick way
NSNotifications
You set up a notification to fire when your downloads are done, then listen for that notification elsewhere in the application.
This article explains how;
https://blog.bobthedeveloper.io/pass-data-with-nsnotification-in-swift-3-73743723c84b
Proper way
Competition handlers
https://thatthinginswift.com/completion-handlers/

Where is dataTaskWithRequest method used?

public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask
I know the usage method of above.
public func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask
But what is the usage of this method? I'm confused about how it is used.All examples I see are using the first method. The method returns no data, no error to handle, nor any response? Or is that you somehow envelope this as a task and later run in a queue?
You use this latter method if you've specified a delegate for a custom NSURLSession. The data is not returned to the closure, but rather the session calls your delegate's didReceiveData, which you have to implement separately.
It takes more work to implement the delegate methods, though, so you generally only do that where you absolutely have to (e.g. you want to process data as it comes in rather than waiting for all of the data to come in; you need delegate methods for custom handling of redirects and challenges; you are doing background NSURLSession with download or upload tasks rather than data tasks; etc.).
For example, to issue a simple GET request, expecting JSON response, you might define your class to conform to URLSessionDelegate, URLSessionDataDelegate, and, optionally, URLSessionTaskDelegate (called NSURLSessionDelegate, NSURLSessionDataDelegate and NSURLSessionTaskDelegate, respectively, in Objective-C and Swift 2), and then do something like the following in Swift 3:
var responseData: Data?
func performRequest() {
let url = URL(string: "http://ip.jsontest.com/")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
responseData = Data()
let task = session.dataTask(with: request)
task.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
responseData!.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard error == nil else {
print(error!)
return
}
do {
guard let jsonObject = try JSONSerialization.jsonObject(with: responseData!) as? [String: AnyObject] else {
print("response was not JSON dictionary")
print("responseString: \(String(data: responseData!, encoding: .utf8))")
return
}
print(jsonObject)
} catch let parseError {
print("JSON Error: \(parseError)")
}
}
Or, in Swift 2:
var responseData: NSMutableData?
func performRequest() {
let url = NSURL(string: "http://ip.jsontest.com/")!
let request = NSMutableURLRequest(URL: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
responseData = NSMutableData()
let task = session.dataTaskWithRequest(request)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
responseData!.appendData(data)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
guard error == nil else {
print(error!)
return
}
do {
guard let jsonObject = try NSJSONSerialization.JSONObjectWithData(responseData!, options: []) as? [String: AnyObject] else {
print("response was not JSON dictionary")
print("responseString: \(String(data: responseData!, encoding: NSUTF8StringEncoding))")
return
}
print(jsonObject)
} catch let parseError {
print("JSON Error: \(parseError)")
}
}
Clearly, you wouldn't do this unless you needed some of the more complicated delegate methods, too, but I wanted to show a minimalist implementation (which helps us appreciate the completion handler rendition).
public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask
returns data, response and error directly in a completion handler.
public func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask
is used in conjunction with the URLSessionDataDelegate protocol. A couple of methods are required to be implemented. During the several stages the delegate methods are called to process the data.
The protocol method provides finer control of the loading process, for example to handle credential requests and redirection control.