Passing closure in Swift - swift

I have a function that I've been using in my code, but I need to pass a completion handler to it now (so I can exit a dispatch_group). I didn't want it to effect the rest of my code so I tried changing the namespace from this:
func uploadRecordable<T: Recordable>(instanceConformingToRecordable: T, database: CKDatabase)
to this:
func uploadRecordable<T: Recordable>(instanceConformingToRecordable: T, database: CKDatabase, completionHandler: (())? = nil)
When I go to pass a closure to it like so:
cloud.uploadRecordable(testRecordable, database: database) {
dispatch_group_leave(forCloudToBeClearOfTestRecord)
}
I get this error:
Function produces expected type 'Void' (aka '()'); did you mean to call it with '()'?
I think that because the completionHandler: (())? argument has a default value of nil the compiler is confused by the closure, but when I tried this I still get same error:
cloud.uploadRecordable(testRecordable, database: database, completionHandler: {
dispatch_group_leave(forCloudToBeClearOfTestRecord)
})
I haven't passed a lot of closures, so I could be doing it wrong, but (())? has worked for me in the past... Also, if it makes any difference this is being called in my tests not my regular code.
Any help on what I'm missing here?
-EDIT-
After changing the closure argument to (() -> ())? I get this error:
Cannot invoke `uploadRecordable` with an argument list of type '(MockRecordable?, database: CKDatabase, completionHandler: (() -> ())?)'
The first two arguments have been working fine, but it doesn't like my closure still. Should I be putting something on the first line (e.g. var in) even though I'm not giving it any variables. Thanks in advance.

In the function declaration, rather than declaring the parameter as completionHandler: (())?, you want completionHandler: (() -> Void)? or completionHandler: (() -> ())?.
Thus:
func uploadRecordable<T: Recordable>(instanceConformingToRecordable: T, database: CKDatabase, completionHandler: (() -> Void)? = nil) { ... }
Or
func uploadRecordable<T: Recordable>(instanceConformingToRecordable: T, database: CKDatabase, completionHandler: (() -> ())? = nil) { ... }

Related

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.

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

Swift trailing closure stopped working when I moved the class into a Framework

I had a LocationManager class the performs various tasks and uses trailing closure.
Here is a method signature :
func getAdresseForLocation(location: CLLocationCoordinate2D, addressType: LocationManagerAddressType, completion: (Address?) -> Void)
For various reasons, I decided to move some file into a Framework, donc I declared my class and method public as follows :
public func getAdresseForLocation(location: CLLocationCoordinate2D, addressType: LocationManagerAddressType, completion: (Address?) -> Void)
But now I get a compilation error from the trailing closure :
Cannot convert value of type '(Address?) -> ()' to expected argument type '(Address?) -> (Void)'
I tried to change the return type to (), (Void), return Void or (Void) from the closure, nothing works.
Do you have any idea what's going on?
Thakns.
Try:
completion: ((Address)? -> Void))

The meaning of urlSession.dataTaskWithRequest(request)

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)")
}

parameters with optional closures in swift

I'm using optional closures, but can't find a way to pass on a parameter.
Searched everywhere, tried all suggestions, but can't get it to work.
My code:
func DoAlert(title: String
, message: String
, actions: String
, sender: AnyObject?
, Ctlr : UIViewController
, SegueString: String?
, YesClosure: ()->() = {}
, NoClosure: ()->() = {}
, StartClosure: ()->() = {}
, EndClosure: ()->() = {}
) {
if (actions.rangeOfString("Ok") != nil {
alert.addAction(UIAlertAction(title: "OK", style: .Default ) { action -> Void in
EndClosure()
})}
} // end function
I want to add a closure for Ok, where the 'self' parameter is needed.
Something like below:
// add to func doAlert:
, OkClosure: (AnyObject)->() = {}
// add to action Ok (before the EndClosure:
OkClosure(sender!)
Getting error on first line:
AnyObject is not subtype of ()
If I leave AnyObject out of first line, Getting error:
Cannot convert the expression's type 'AnyObject' to type '() => ()'
All other trials give me similar 'Tuple' errors.
How do I code the passing of parameters in the optional closures in my code?
Firstly, to use closures as an argument for a function, you should declare them like so:
func myFunc(closure: (Int) -> Void) {
// Now I can call closure like so:
let myInt = 10
closure(myInt)
}
(As pointed out by #Airspeed Velocity, the parenthesis around Int are not strictly required because there is only one argument. Whether you include them is just personal preference)
Secondly, you can modify the previous function to include an optional closure, as follows:
(Note the ? and parenthesis around the closure that indicate the closure is an optional, not the return type)
func myFunc(closure: ((Int) -> Void)?) {
// Now when calling the closure you need to make sure it's not nil.
// For example:
closure?(10)
}
Thirdly, to add a default value of nil, which is what it looks like you're trying to do with the = {} on the end of YesClosure: ()->() = {}, you could do:
func myFunc(closure: ((Int) -> Void)? = nil) {
// Still need to make sure it's not nil.
if let c = closure {
c(10)
}
}
Finally, just as a note, you can set the names of the arguments of the closure, which can make it easier to identify what you're passing to the closure when calling it. For example:
(Note - here parenthesis are required around value: Int)
func myFunc(closure: ((value: Int) -> Void)) {
closure(value: 10)
}
Even more finally, you could use typealias. According to the documentation:
A type alias declaration introduces a named alias of an existing type into your program.
Here's an example of how to use it with a closure:
typealias MyClosureType = () -> Void
func myFunc(closure: MyClosureType) {
closure()
}
Hope that helps!
I think I found it. I can't use parameters in the closure when I call func. In func itself I need to define the parameters used (closure: (sender: AnyObject) -> Void), make sure the variables are defined (or provided as a separate parameter) and add them to the closure call.
#IBAction func buttonPressed(sender: AnyObject) {
myFunc (sender, doClosure)
}
func myFunc(sender: AnyObject, closure: (sender: AnyObject) -> Void) {
// Now I can call closure like so:
closure (sender: sender)
}
func doClosure(sender: AnyObject) {
println("sender = \(sender)")
}