Swift: How to download synchronously? - swift

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)

Related

Swift HTTP Request Completion Block not Working Properly

I have a weird problem with HTTP requests in Swift. I have the following simple code:
let session = URLSession.shared
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
// Check the response
print(error)
print(response)
})
task.resume()
Running this code in a Xcode playground outputs the print statements. However, executing it in a standard Xcode project does not give any output.
I wonder if I am missing something in my code or if something is wrong with my Xcode setup.
Thanks in advance!
Edit:
Here's a screenshot of Xcode
As Tarun indicated, your executable is exiting before your call can be made. One way you can wait before exiting is to use a DispatchGroup like the following.
let session = URLSession.shared
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let group = DispatchGroup()
group.enter()
let task = session.dataTask(with: url, completionHandler: { data, response, error in
// Check the response
print(error)
print(response)
group.leave()
})
task.resume()
group.wait()
Looking at this screenshot -
It doesn't seem like any part of your code is executing at all. There's no entry point defined.
You can try following -
#main
struct NewApp {
static func main() {
// Paste all of your code here
// Put breakpoints and inspect what you want
}
}

datataskwithURL() completion block not being called. Xcode swift

I am trying to write a basic function to make an HTTP Get request and parse the xml data that comes back. I already have completed the section for parsing XML from a local file, but i can't seem to get any data from the server. I tested this code below, but the completion block does not even run, for information to be passed back from the server. Any suggestions please.
func getDatafromURL (url: String) {
guard let urlforRequest = NSURL(string: url) else {
print("Error: cannot create URL")
return
}
let request = NSURLRequest(URL: urlforRequest)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
print(response)
print(error)
}
task.resume()
}

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

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

using Swift to get contents of URL using session.dataTaskWithRequest() - data doesn't convert to NSString

Why would my code below successfully return with data, with a statusCode of 200 but fail to convert the returned NSData to an NSString?
var session: NSURLSession
func init() {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: config)
}
func getStatic(url:NSURL) {
let request = NSMutableURLRequest(URL: url)
let dataTask = session.dataTaskWithRequest(request) {(data, response, error) in
if error != nil {
// handle error
} else {
// data has a length of 2523 - the contents at the url
if let httpRes = response as? NSHTTPURLResponse {
// httpRes is 200
let html = NSString(data:data, encoding:NSUTF8StringEncoding)
// **** html is nil ****
}
}
}
dataTask.resume()
}
The code is indeed correct.
The URL I was trying to load had non-UTF8 characters and so the encoding attempted by NSString(data:data, encoding:NSUTF8StringEncoding) failed.
Removing none UTF8 characters fixed the problem.
Or selecting an appropriate encoding, NSISOLatin1StringEncoding for my content, also worked.
It looks like it should be fine, to me at least. I'm just dabbling in Swift, but I've done my enclosures (not sure if thats the right name) slightly changed like below. Did you try converting data to NSString prior to your if let httpRes = response as? NSHTTPURLResponse line? Maybe the data variable doesn't actually have the html in it. I have code written almost exactly the same with the changes below that I'm able to successfully convert data to a NSString.
let dataTask = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if error != nil {
// handle error
} else {
if let httpRes = response as? NSHTTPURLResponse {
let html = NSString(data:data, encoding:NSUTF8StringEncoding)
}
}
})
Hope it somehow helps.

Swift: NSData(contentsOfURL) crashing on XCode 6.1

Before upgrading to XCode6.1 I was using the method NSData.dataWithContents() and it was working perfectly all the images were downloading. Today I have updated to XCode 6.1 and it forces me to use the function like this:
NSData(contentsOfURL: NSURL(string: completeUrl)!)!
and when I run the application it crashes on it with message:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have tried many things but nothing works. I am looking for any simple alternative for this to download images from a given URL.
Since the initalization of NSURL may fail due to several reasons you should better use it this way:
if let url = NSURL(string: completeUrl) {
if let data = NSData(contentsOfURL: url) { // may return nil, too
// do something with data
}
}
More better way to download files is:
let request:NSURLRequest = NSURLRequest(URL: NSURL(string: completeUrl)!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (response:NSURLResponse!, imageData:NSData!, error:NSError!) -> Void in
var filePath:String = pathString + "/" + fileName
imageData.writeToFile(filePath, atomically: true)
})
It is working very nicely and also it gives you more control on the request.