The meaning of urlSession.dataTaskWithRequest(request) - swift

When I read the book about swift in the Network Development chapter, I met some code which I cannot understand. The code is as follows:
let sessionTask = urlSession.dataTaskWithRequest(request) {
(data, response, error) in
handler(response, data)
}
the prototype of this function in swift is:
public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask
As you can see, the prototype has 2 parameters, one is request, another is completionHandler. But in the above code, it also has one parameter. And also I cannot understand the code in the curly braces, where do the 3 variable data, response, error come from? I cannot find any definition of the 3 variables. Who can help me understand the code, thanks in advance.

It is called a trailing closure, it's a cleaner way of passing a function to another function if that function is the last argument. The same code can be written as:
let sessionTask = NSURLSession.sharedSession()
let request = NSURLRequest()
sessionTask.dataTaskWithRequest(request, completionHandler: {(data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
})
If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. A trailing closure is a closure expression that is written outside of (and after) the parentheses of the function call it supports
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID102
func aFunction(callback: (done: Bool) -> Void) {
let finished = true
callback(done: finished)
}
aFunction { (done) -> Void in
print("we are done \(done)")
}

Related

What does let NAME1 = NAME2( params ) { var1, var2 in mean in Swift?

I am new to Swift and some constructs I can't even read. For example:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
What is it? Function call because of ()? Or inline anonymous class declaration because of {}? Or enumeration because of in?
That is trailing closure syntax. URLSession.dataTask(with:) returns a URLSessionDataTask instance and its last input argument is a closure of type (Data?, URLResponse?, Error?) -> Void, where the async network request response or an error is returned.
You can use the URLSessionDataTask object to call start or cancel the network request.
If you don't use trailing closure syntax, it might be more clear what you're seeing:
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
// you can use data, response and error inside the closure here
})
Which is equivalent to:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
The trailing closure syntax simply allows use to omit the input argument label for the last input argument when it is a closure and put the closure with the curly brackets after the closing parentheses of the function call.
In Swift this is called a completion handler. It's basically an anonymous function that you pass in which the function you are calling will call when it's done. In Swift this can either be written as a normal parameter or as a closure after the function call, which is what is happening in your example.
You can see in the function definition from the documentation
func dataTask(with request: URLRequest, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
That there are two parameters to the function request and completionHandler
In Swift, when the final parameter is a function it can be, and often is, written as a closure after the function call.
NOTE:
As was suggested in another comment, this is NOT the return value. You'll see this function has a return value of URLSessionDataTask which is being assigned to task in your example

Can't infer generic type on static function with completion block

I have a static function that uses generics, but I can't get it to infer the generic type when it's called. The function:
static func getDocument<T: JSONDecodable>(_ document: String, fromCollection collection: FirebaseStorage.FirestoreCollections, completion: #escaping (_ decodedDoc: T?, _ error: Error?) -> ()) {
let docRef = firestore.collection(collection.rawValue).document(document)
docRef.getDocument { documentSnapshot, error in
guard error == nil,
let docData = documentSnapshot?.data(),
let decodedDoc = T(json: docData) else {
completion(nil, error)
return
}
completion(decodedDoc, nil)
}
}
Called using:
FirebaseClient.getDocument(
id,
fromCollection: FirebaseStorage.FirestoreCollections.users) { (profile, error) in
}
This gives the error: Generic parameter 'T' could not be inferred. How can I make the generic part of the function work?
FirebaseClient.getDocument(
id,
fromCollection: FirebaseStorage.FirestoreCollections.users) { (profile: ProfileType?, error) in
}
You'll need to let Swift know what type profile is where I've added ProfileType. That should do it!
Kane's answer is good, but a more flexible approach is to pass the type directly. For example, this makes it possible to have an optional completion handler, or to ignore the parameter with _ if you don't care about it. (That said, this approach is a little longer to type, so sometimes Kane's way is better.)
static func getDocument<T: JSONDecodable>(_ document: String,
ofType: T.Type,
completion: #escaping (_ decodedDoc: T?, _ error: Error?) -> ())
This makes everything explicit. You call it this way:
FirebaseClient.getDocument(id, ofType: ProfileType.self) { (profile, error) in ... }
Note that there's no need to use the ofType parameter for anything. It's just there to specialize the generic.
This is pretty close to how Decodable works, and is applicable to a lot of problems. But Kane's solution is also handy at times if it's more convenient.

Issue with Alamofire challenge delegate and escaping closure

I am using AF and using it's delegate to catch the authentication challenge returned by my server.
func connectGetRequest(_ url : URL){
let sessionManager = Alamofire.SessionManager.default
sessionManager.request(url).responseString { response in
print("Response String: \(response.result.value)")
}
let delegate: Alamofire.SessionDelegate = sessionManager.delegate
delegate.taskDidReceiveChallengeWithCompletion = { session, task, challenge, completionHander in
print("session is \(session), task is \(task) challenge is \(challenge.protectionSpace.authenticationMethod) and handler is \(completionHander)")
if(challenge.protectionSpace.authenticationMethod == "NSURLAuthenticationMethodServerTrust"){
completionHander(.performDefaultHandling,nil)
}else{
print("challenge type is \(challenge.protectionSpace.authenticationMethod)")
// Following line give me the error: "passing non-escaping parameter 'completionHander' to function expecting an #escaping closure"
self.handleAuthenticationforSession(challenge,completionHandler: completionHander)
}
}
delegate.dataTaskDidReceiveData = {session , task, data in
print("received data \(data)")
}
}
func handleAuthenticationforSession(_ challenge: URLAuthenticationChallenge,completionHandler: #escaping (Foundation.URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
Authhandler.handleChallenge(forURLSessionChallenge: challenge, completionHandler: completionHandler)
}
issue I have:
If I use the code above as it is, I get
error: "passing non-escaping parameter 'completionHander' to function expecting an #escaping closure"
If I make the parameter of the function handleAuthenticationSession non escaping, I get :
func handleAuthenticationforSession(_ challenge: URLAuthenticationChallenge,
completionHandler: (Foundation.URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
}
error: "Closure use of non-escaping parameter 'completion' may allow it to escape"
Also, handleChallenge method from AuthHandler class (which is a part of obj-c framework) looks like following.
-(BOOL)handleChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler;
So basically I am stuck in a deadlock while I use Alamofire's closure syntax to delegate the auth challenge.
It seems to me the missing piece of your question is whether the completion handler in Authhandler.handleChallenge is escaping. It is, right?
But the taskDidReceiveChallengeWithCompletion completionHandler is non-escaping. So you're trying to figure out how to let it escape when it's not allowed to escape.
Looking at the Alamofire source code, about 3 months ago, they changed that completionHandler to be #escaping! See here: https://github.com/Alamofire/Alamofire/commit/b03b43cc381ec02eb9855085427186ef89055eef
You need to update to a version of Alamofire after that PR got merged or you need to figure out how to handle the completionHandler in a fully non-escaping way. Meaning, your Authhandler.handleChallenge can't have an escaped completionHandler.

Confusing closures and completion handles

Im a new programmer and am very lost.
I am taking this online iOS dev course and I was configuring collection view cell.
However, closures and completion handles were used and it was never mentioned before.
import UIKit
class PersonCell: UICollectionViewCell {
#IBOutlet weak var img: UIImageView!
func configureCell(imgUrl: String) {
if let url = NSURL(string: imgUrl) {
downloadImg(url)
}
}
func downloadImg(url: NSURL) {
getDataFromURL(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else {return}
self.img.image = UIImage(data: data)
}
}
}
func getDataFromURL(url: NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError?) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
completion(data: data, response: response, error: error)
} .resume()
}
}
Can someone explain to me what the completion handler is doing after the "getDataFromURL" function. Also what are the closures doing? is "(data, response, error)" getting passed around? How does swift know that "data" is suppose to be NSData and etc in the "(data, response, error)"?
What does the closure after the "dataTaskWithURL" do (is it setting up the completion handler"?
Thank you!
These are good questions!
A closure is simply a collection (aka block) of lines of code that you can treat like a variable and execute like a function. You can refer to a closure with a variable name and you can pass a closure around as a parameter in function calls just like any other variable, eventually executing the code when appropriate. A closure can accept certain parameters to use in its code and it can include a return value.
Example:
This is a closure that accepts two strings as parameters and returns a string.
let closure: (String, String) -> String = { (a: String, b: String) -> String in
return a + b
}
Thus, the following will print "Hello Jack!":
print(closure("Hello ", "Jack!"))
A closure also has a variable type (just like "hello" is a String and 1 is an Int). The variable type is based on the parameters that the closure accepts and the value that the closure returns. Thus, since the closure above accepts two strings as parameters and returns a string, its variable type is (String, String) -> String. Note: when nothing is returned (i.e. the return type is Void), you can omit the return type (so (Int, String) -> Void is the same thing as (Int, String)).
A completion handler is a closure that you can pass to certain functions. When the function completes, it executes the closure (e.g. when a view finished animating onto the screen, when a file finished downloading, etc.).
Example:
"Done!" will be printed when the view controller is finished presenting.
let newClosure: () -> Void = { () -> Void in
print("Done!")
}
let someViewController = UIViewController(nibName: nil, bundle: nil)
self.presentViewController(someViewController, animated: true, completion: newClosure)
Let's focus on the getDataFromURL function you wrote first. It takes two parameters: a variable of type NSData and a closure of type (NSData?, NSURLResponse?, NSError?) -> Void. Thus, the closure (which is named completion) takes three parameters of types NSData?, NSURLResponse?, and NSError?, and returns nothing, because this is how you defined the closure in the function declaration.
You then call getDataFromURL. If you read the documentation, you'll see that the closure you pass to this function as the second parameter is executed when the load task is complete. The function declaration for dataTaskWithURL is what defines the variable types that the closure accepts and returns. Within this closure, you are then calling the closure you passed to the getDataFromURL function.
Within this latter closure (the one you define in downloadImg when you are calling getDataFromURL), you are checking to see if the data that you downloaded is not nil, and if not, you are then setting the data as an image in a UIImageView. The dispatch_async(dispatch_get_main_queue(), ...) call simply ensures that you are setting the new image on the main thread, as per Apple's specifications (you can read more about threads elsewhere).
make an typealias to understand this is easy :
typealias Handle = (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void
//the func should be
func getDataFromURL(url: NSURL, completion: Handle)
//when you call it. it needs an url and an Handle
getDataFromURL(url:NSURL, completion: Handle)
// so we pass the url and handle to it
getDataFromURL(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else {return}
self.img.image = UIImage(data: data)
}
}
//setp into the func
func getDataFromURL(url: NSURL, completion: Handle){
// call async net work by pass url
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
// now data / response / error we have and we invoke the handle
completion(data: data, response: response, error: error)
} .resume()
}
hope it be helpful :D

Pass scope to a named function, rather than a closure

I would like to separate the data processing of my NSURLSession into a separate method.
When making a URL request, I rely on the enclosing scope to provide me the user's callback.
This works:
static func makeRequest(url: String, callback: APICallback) {
let urlObject = NSURL(string: url)
var request = createRequest(urlObject!, method: "GET") // internal
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
// do some basic parsing, error checking, then...
callback(data, nil)
}
task.resume()
}
There's rather a lot of basic parsing and error checking I'd like to do at the application level, however, so I want to define and pass a function instead of a closure to the dataTaskWithRequest method:
static func makeRequest(url: String, callback: APICallback) {
let urlObject = NSURL(string: url)
var request = createRequest(urlObject!, method: "GET") // internal
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request, completionHandler: gotResponse)
task.resume()
}
static private func gotResponse (nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
// Do my parsing and handling here, instead.
// OOPS! Don't have access to the callback. :(
}
This all leads me to my question, which, despite the lengthy example, is about language features. Can I pass some captured scope to this method? In Javascript I could accomplish this using Function.prototype.bind, but I'm not sure how to do it in Swift.
This seems like a good example of when to use a curried method. Declare the function like this with a first parameter of an APICallback. Note the brackets.
static private func gotResponse(callback: APICallback)(nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
// use callback: like normal
}
Then, use it like this:
var task = session.dataTaskWithRequest(request, completionHandler: gotResponse(callback))
(apologies, the syntax might not be 100% correct since your code isn’t stand alone so I can’t test it fully)
Curried functions are a little finicky and had some bugs in 1.1 (though they got fixed in 1.2), so instead of using the language support for them, you could try hand-rolling if the above doesn’t work, something like:
static private func gotResponse(callback: APICallback) -> (nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
return { data, response, error in
// put the common code, capturing callback:, in here...
}
}