Swift: Block mainThread until function finished loading data / NSURLSession with completionHandler - swift

I want to create a function which loads data from an url and then returns the responseData as NSData. I want to block the mainThread until the data is finished. Here is what I have so far:
Function:
typealias CompletionBlock = (NSData!, NSURLResponse!, NSError!) -> NSData
func requestURL(targetUrl: String, httpMethod: String, httpBody: String, completion: CompletionBlock){
// REQUEST
let target = NSURL(string: targetUrl) // URL
let request = NSMutableURLRequest(URL: target!) // REQUEST
// HTTP-METHOD
var method = httpMethod
if method != "POST" { method = "GET" } // STANDARD: GET
request.HTTPMethod = method
// HTTP-BODY
let body = httpBody
if body != "" {
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)
}
// NSURLSession
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: completion) // Compiler error!
task.resume()
}
Call:
requestURL(targetURL, "GET", "", { (responseData: NSData!, response: NSURLResponse!, error: NSError!) -> NSData in
if responseData != nil {
let resultString = NSString(data: responseData, encoding: NSUTF8StringEncoding) // NSASCIIStringEncoding
println("DATA:\n\(resultString)")
}
if error != nil {
println("ERROR:\n\(error)")
}
return responseData
})
I get an error at within the func in line 21:
let task = session.dataTaskWithRequest(request, completionHandler: completion)
Compiler: "Cannot invoke 'dataTaskWithRequest' with an argument list of type '(NSMutableURLRequest, completionHandler: completionBlock)"

As for question issue: typealias CompletionBlock = (NSData!, NSURLResponse!, NSError!) -> NSData
Your completion handler returns NSData but it shouldn't return anything as in declaration:
func dataTaskWithRequest(_ request: NSURLRequest,
completionHandler completionHandler: ((NSData!,
NSURLResponse!,
NSError!) -> Void)?) -> NSURLSessionDataTask
This caused a type error, because you had provided a wrong closure type.
And it is quite reasonable, because dataTaskWithRequest is designed to be asynchronous. It creates an HTTP request for the specified URL request object, and calls a handler upon completion.
If you really want to make a synchronous request you can use an old NSURLConnection API with sendSynchronousRequest, but you shouldn't do it, because synchronous requests are a very bad design choice: they block main UI thread and there is no way to cancel that request except when it errors on its own. That's why Apple created a new NSURLSession API, based on completion handlers and now deprecated synchronous requests in iOS 9.

Doing synchronous request is very bad, but this will do
let request = NSURLRequest() //set all url and method and so
var response: NSURLResponse?
var error: NSError?
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
important synchronous requests was removed in swift 2.0.

As Daniel said, this is a bad practice in general. Any time, you have to go out and get data and wait for a response, you should be handling that asynchronously. If not, your UI is going to hang up on the main thread, and the user is likely to think that your app has crashed.
Working asynchronously, you will have to keep in mind that your main thread is still working and won't have that data until it receives it in the completion block. If it's imperative, you should throw up a UI element to let the user know you are waiting ie. UIActivityIndicatorView

Related

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: How to download synchronously?

I am using following code, to download some files:
let url = NSURL(string:"https://www.example.org/")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
NSLog("%#", "Error: \(error)");
return
}
NSLog("Loaded %i bytes", data!.length)
}
task.resume()
I want to process some of the files and quit my application after downloading. Therefore I need to know, when the download process is finished. The best would be, if there is a way to do this synchronously (no problem, if UI is blocked - it is just a spash screen with a progressbar). But as far as I understood this topic after some research, this is not possible in Swift any more...
I did wrap this code in a function, therefore I can't just add some code after the NSLog statement. What I need to know is: When did the last file finish downloading? How can I retrive this information?
EDIT: This code did work for me (but be aware, its deprecated!):
// download file synchonously ////////////////////////////////////////////////////
func downloadSync(fromURL: String, toPath: String) {
let request = NSURLRequest(URL: NSURL(string: fromURL)!)
var response: NSURLResponse?
do {
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)
data.writeToFile(toPath, atomically: true)
} catch {
print("Error while trying to download following file: " + fromURL)
}
}
After you invoke task.resume(), the download starts.
When the download does complete (or an error is received) the code
inside the { } following dataTaskWithURL is called. That's a closure and it's called asynchronously.
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
// This code is executed asynchronously after data has been received
}
task.resume()
Inside the closure you receive 3 params:
data: the NSData you requested
response: the whole NSURLResponse
error: an NSError object
These 3 values are optional, so could be nil.
E.g. error could be populated and data could be nil.
The synchronous way [DEPRECATED in iOS 9]
This approach has been deprecated in iOS 9 and you should NOT use it, however here's the code
var response: NSURLResponse?
var error: NSError?
let urlData = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)

Prevent redirect response with Alamofire in Swift

I'm looking for example code how to prevent redirect response (status code 3xx) when request web api. I'm using Swift with Alamofire 1.2.
I have tried:
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
return nil
}
but not work
I've also tried: https://github.com/Alamofire/Alamofire/pull/350/files and have changed my own code to:
var acc = self.txtAccount.text
var pwd = self.txtPassword.text
var url : String = "http://10.1.0.2:8081/wordpress/wp-json/users/me"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "GET"
var credential = "\(acc):\(pwd)"
var authData = credential.dataUsingEncoding(NSUTF8StringEncoding)
var encodedAuthData = authData?.base64EncodedStringWithOptions(nil)
var authValue = "Basic \(encodedAuthData!)"
request.setValue(authValue, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
return request
}
//I've implemented URLRequestConvertible 'Router'. it also have call the same above url
Alamofire.request(Router.Authorize(acc, pwd))
.response({(request, response, data, error) in
println(request)
})
But it's not worked and seem like turned to infinite loop. I tested on Charles.
Alternative (code snippet) solution using AlamoFire 2.4 (Xcode7). In my case, I always expect a redirect. (I am unpacking a shortened link.) If the completion in the request.response call runs, that is an error to me.
func printRedirectUrl() {
// taskWillPerformHTTPRedirectionWithCompletion: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest, NSURLRequest? -> Void) -> Void)?
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirectionWithCompletion = { session, task, response, request, completion in
// request.URL has the redirected URL inside of it, no need to parse the body
print("REDIRECT Request: \(request)")
if let url = request.URL {
print("Extracted URL: \(url)")
}
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities
return
}
// We expect a redirect, so the completion of this call should never execute
let url = NSURL(string: "https://google.com")
let request = Alamofire.request(.GET, url!)
request.response { request, response, data, error in
print("Logic Error, response should NOT have been called for request: \(request)")
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities - just in case
}
}
REDIRECT Request: { URL: https://www.google.com/ }
Extracted URL: https://www.google.com/
In Swift 4,
let delegate = Alamofire.SessionManager.default.delegate
delegate.taskWillPerformHTTPRedirection = { (session, task, response, request) -> URLRequest? in
// print("REDIRECT Request: \(request)")
return nil
}
Hello its actually pretty simple
Alamofire has a redirector that will
Example
let request = AF.request("https://google.com",method: .post,parameters: parameters)
.cURLDescription { description in
debugPrint(description)
}
let redirector = Redirector(behavior: .doNotFollow)
request.redirect(using: redirector)
with that it wont redirect
its also in the docs in the advanced usage section
It looks like returning nil can possibly cause a deadlock. Instead, try to create a new NSURLRequest with the same original URL. See #jhersh's notes in a previous Alamofire PR along with the comments and implementation in his tests.
How to Stop a Redirect
func disallowRedirect() {
let URL = "http://google.com/"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
return NSURLRequest(URL: NSURL(string: URL)!)
}
let request = Alamofire.request(.GET, URL)
request.response { request, response, data, error in
println("Request: \(request)")
println("Response: \(response)")
println("Data: \(NSString(data: data as! NSData, encoding: NSUTF8StringEncoding))")
println("Error: \(error)")
}
}
disallowRedirect()
The fact that you cannot pass nil into the NSURLSessionTaskDelegate method's completionHandler looks like a bug. I'm going to file a radar for this and I'll post a link to the bug report once I'm finished.
I don't know if your version of Alamofire has a support for public delegate. Last time I checked delegate was private. I am using the changes made by #jhersh. You can check his additions and how to use delegate by followin github pr. https://github.com/Alamofire/Alamofire/issues/314

Clousure code won't execute wrapped in a function

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

NSURLConnection sendAsynchronousRequest returning all nil in Completion Block (Swift)

I'm trying to ween myself off AFNetworking and use NSURLConnection from Swift. Although seemingly simple, I can't get this short block of code to return anything in the completion handler block. All three objects (response, data, error) are nil when I break within the completion handler block and po them in the console. It's definitely making the connection, as I can see it happen on the server end.
let url = NSURL(string: "http://192.168.0.5:8081/ac")
let urlR = NSURLRequest(URL: url)
let q = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(urlR, queue: q, completionHandler: { (response: NSURLResponse!, data: NSData!, error:NSError!) -> Void in
})
I can't seem to find anyone else having this issue, and have found several examples of people successfully using what as far as I can see is exactly this code. What am I doing wrong here?