Usage of closures with multiple arguments in swift

2019-05-26 07:06发布

问题:

This question is largely based on this one:

Link

The main difference being that I want to pass in arguments to the closure as well. Say I have something like this:

func someFunctionThatTakesAClosure(completionClosure: (venues: Dictionary<String, AnyObject>, error: NSError) -> ()) {
        // function body goes here
        var error: NSError?
        let responseDictionary: Dictionary<String, AnyObject> = ["test" : "test2"]
        completionClosure(venues: responseDictionary, error: error!)
    }

No error here. But when I call this function in my main view controller I have tried several ways but all of the result in different errors:

venueService.someFunctionThatTakesAClosure(completionClosure(venues: Dictionary<String, AnyObject>, error: NSError){

        })

or like this:

venueService.someFunctionThatTakesAClosure((venues: Dictionary<String, AnyObject>, error: NSError){

        })

or even like this:

venueService.someFunctionThatTakesAClosure(completionClosure: (venues: Dictionary<String, AnyObject>, error: NSError) -> (){

            });

I'm probably just way tired, but any help would be greatly appreciated!

回答1:

venueService.someFunctionThatTakesAClosure({
  venues, error in
  // do stuff
})

you can optionally specify the types (but since the compiler knows what types the closure is supposed to provide, you don't strictly have to:

venueService.someFunctionThatTakesAClosure({
  (venues: Dictionary<String, AnyObject>, error: NSError) -> () in
  // do stuff
})

But I see another issue in your calling code:

completionClosure(venues: responseDictionary, error: error!)
//            No Bueno. What if there's no error?    ^^^^^^

You shouldn't force unwrap the error since it's entirely possible that it's still nil. Force unwrapping nil will cause an error. So you want this:

completionClosure(venues: responseDictionary, error: error)

AND you want to change your closure to take an optional error. In total:

func someFunctionThatTakesAClosure(completionClosure: (venues: Dictionary<String, AnyObject>, error: NSError?) -> ()) {
    …
    completionClosure(venues: responseDictionary, error: error)
}

// when calling:
venueService.someFunctionThatTakesAClosure({
  (venues: Dictionary<String, AnyObject>, error: NSError?) -> () in
  // do stuff
})

If you'd like to pass additional arguments, keep in mind swift is optimized for closures to be passed as the last argument (and it's a widely followed convention in objective-c APIs):

// in venueService:
func someArgAndClosureFunction(arg1: String, arg2: Int, 
                  completionClosure: (venues: Dictionary<String, AnyObject>, error: NSError?) -> ()) {
    // do stuff
}

// When calling:
venueService.someArgAndClosureFunction("string arg 1", arg2: 10) {
  (venues: Dictionary<String, AnyObject>, error: NSError?) -> () in
  // do stuff
}

In this example I've used the trailing closure syntax which allows you pass the closure outside the function call parens (but it is still passed as the last argument).



回答2:

As your error is optional, I would make it an optional argument of the passed function:

func someFunctionThatTakesAClosure(completionClosure: (venues: Dictionary<String, AnyObject>, error: NSError?) -> ()) {
  // function body goes here
  var error: NSError? = nil
  let responseDictionary: Dictionary<String, AnyObject> = ["test" : "test2"]
  completionClosure(venues: responseDictionary, error: error)
}

It can then be invoked, passing a closure as follows:

someFunctionThatTakesAClosure { println($0); println($1) }

Note, the above is one of the shorthand syntax variants for creating closures.



回答3:

I think you were probably just tired :)

You're saying that the difficulty you are having is in calling the method, right? None of the examples you gave are passing a valid argument, which should be a closure of type:

(Dictionary<String, AnyObject>, NSError) -> ()

So there are a couple of things you could do...

(a) assign a conforming closure to a variable and pass the variable as the argument to the method:

let myClosure: (Dictionary<String, AnyObject>, NSError?) -> () = { venues, error in
    for (key, _) in venues {
        println(key)
    }
}

venueService.someFunctionThatTakesAClosure(myClosure)

(b) pass a trailing closure as the argument to the method call:

venueService.someFunctionThatTakesAClosure {
    venues, error in
    for (key, _) in venues {
        println(key)
    }
}


回答4:

The syntax for passing in parameters to closures revolve around the in keyword.

Sorry for the link url...but check out http://fuckingclosuresyntax.com/ and notice in the options where in appears.

see array.sort({ (item1, item2) in return item1 < item2 })

Which takes in item1 and item2 as 2 inputs