7 ways to pass a closure as an argument in Swift
Table of Contents
There are many functions and methods in iOS that take a closure as one of their arguments.
For example, the view animate function takes a closure that containing the changes to commit to the views when animate.
class func animate(withDuration duration: TimeInterval,
animations: @escaping () -> Void)
Or the filter method, which takes a function that takes an element of the sequence as its argument and returns a boolean value indicating whether the element should be included in the returned array or not.
func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]
We have many ways to pass a closure into these functions. Some of them do not even look like closure. Let's learn all of them in this article.
I will use filter
and sorted
methods as an example. I will start with the longest form to the shortest one. Each of them expresses the same functionality but in a different form.
Function
Function in Swift is just another variation of closure, so we can use them interchangeably in a place where expected closure. The only requirement here is that the method signature must match the signature of the closure parameter.
Here I have an array of random integer range from 1 to 10.
let scores = [3, 8, 1, 7, 4, 2, 5, 6, 9, 10]
As an example, I will do two things with this array.
- I will use the
filter
method to get an array containing only an integer greater than 5. - I will sort an array in descending order using the
sorted
method.
First, we create two functions to pass in as a closure argument.
func greaterThanFive(score: Int) -> Bool {
return score > 5
}
func descendingOrder(lhs: Int, rhs: Int) -> Bool {
return lhs > rhs
}
Then, we can pass it as an argument by reference their name.
let scores = [3, 8, 1, 7, 4, 2, 5, 6, 9, 10]
let greaterThanFiveScores = scores.filter(greaterThanFive)
print(greaterThanFiveScores)
// [8, 7, 6, 9, 10]
let sortedScores = scores.sorted(by: descendingOrder)
print(sortedScores)
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
If you have two almost identical functions where the only difference is argument label, use only a function name would cause an ambiguous error. You also need to specify argument labels along with function names.
In this example, we have two identical functions. The only difference is their argument label, score
and alsoScore
.
func greaterThanFive(score: Int) -> Bool {
return score > 5
}
func greaterThanFive(alsoScore: Int) -> Bool {
return alsoScore > 5
}
// 1
let greaterThanFiveScores = scores.filter(greaterThanFive(score:))
1 Use scores.filter(greaterThanFive)
will cause Ambiguous use of 'filter'
error. You need to use argument label in this case, scores.filter(greaterThanFive(score:))
.
A filter
need a closure of type (Int) throws -> Bool
(or (Int) -> Bool
since casting from (Int) -> Bool
to (Int) throws -> Bool
always success). The greaterThanFive
function has a signature (Int) -> Bool
which match the one that filter
want. The same rule apply to descendingOrder
and sorted
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Closure expressions
Closure expressions are unnamed closures with a more lightweight syntax suit for brief inline and focuses used. Closure expressions provide several syntax optimizations for writing closures in a shortened form without loss of clarity or intent.
General Form
Here is the general form when passing a closure as an argument.
{ (parameters) -> return type in
statements
}
The example below shows a closure expression version of the greaterThanFive
and descendingOrder
function from the previous section:
scores.filter { (score: Int) -> Bool in
return score > 5
}
scores.sorted { (lhs: Int, rhs: Int) -> Bool in
return lhs > rhs
}
Inferring Type From Context
Because the closure is passed as an argument to a method, Swift can infer the types of its parameters and the type of the value it returns from that method.
We can omit the return type, the arrow (->), parameters type, and the parentheses around those inputs.
scores.filter { score in
return score > 5
}
scores.sorted { lhs, rhs in
return lhs > rhs
}
Implicit Returns from Single-Expression Closures
If closures contain only one expression, it can implicitly return the result of their single expression. Hence we can omit the return
keyword.
scores.filter { score in score > 5 }
scores.sorted { lhs, rhs in lhs > rhs }
Shorthand Argument Names
Swift automatically provides shorthand argument names if you omit closure’s argument list from the definition.
The shorthand arguments are named after the position of closure's arguments. It will be in this format $[position of parameters]
, a dollar sign follows by an argument position, e.g., $0, $1, $2.
// 1
// scores.filter { score in score > 5 }
scores.filter { $0 > 5 }
// 2
// scores.sorted { lhs, rhs in lhs > rhs }
scores.sorted { $0 > $1 }
1 $0
refer to the closure's first argument, score
.
2 $0
refer to the closure's first argument, lhs
. $1
refer to the closure's second argument, rhs
.
Operator Methods
Swift documentation put this under the shortest form of closure expression, but I think of it the same way as using function as an argument.
Int type overrides the greater-than operator (>
) to work with its type. If you open up an interface file, you will see something like this.
@frozen public struct Int : FixedWidthInteger, SignedInteger {
public static func > (lhs: Int, rhs: Int) -> Bool
}
>
is a method with two parameters of type Int
and returns a value of type Bool
. This matches the method type needed by the sorted(by:)
method, so we can use the method name, >
as an argument.
scores.sorted(by: >)
Keypath
Key Path expression \Root.value
can be used where functions of (Root) -> Value
are allowed.
Since a key path can be treated like a function, we can use it the same way as mentioned earlier in the first section.
In the following example, I create a new variable as an extension of Int
.
extension Int {
var greaterThanFive: Bool {
return self > 5
}
}
This \Int.greaterThanFive
can be translated into (Int) -> Bool
, which matches the filter
signature, so we can use it as its argument.
scores.filter(\.greaterThanFive)
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Conclusion
I think each style has its own pros and cons, which might suit different needs and preferences. You probably use one or two styles that match your preference, but it is good to know all of them so you understand when you encounter them somewhere else.
Read more article about Swift, Closure, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to make a custom button style with UIButton.Configuration in iOS 15
Learn how to create an outline button style.
How to present a Bottom Sheet in iOS 15 with UISheetPresentationController
In iOS 15, we finally have native UI for a bottom sheet. Let's explore its behavior and limitation.