7 ways to pass a closure as an argument in Swift
source link: https://sarunw.com/posts/different-ways-to-pass-closure-as-argument/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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
Some referring this as point-free style. Point-free style is a programming paradigm in which function definitions do not identify the arguments (or "points") on which they operate. As you can see, we reference greaterThanFive
by its name, leaving other information out.
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 }
This was introduced in Swift 5.1 from SE-0255: Implicit returns from single-expression functions.
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.
The first position starts at zero indexes.
// 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
.
You can't use this shorthand argument if you put the argument list in the closure. These shorthand argument / anonymous arguments are only generate if you don't declare an explicit argument list.
This won't work.
scores.filter { score in $0 > 5 }
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)
This was introduced in Swift 5.2 from SE-0249: Key Path Expressions as Functions
You can easily support sarunw.com by checking out this sponsor.
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.
You may also like
Learn the meaning of @escaping, so you know what to do when you see it or when you need to write one.
Swift ClosurePart 3 in a series Sign in with Apple. In this part, we will see how backend can use the token to sign up/sign in users.
SwiftRead 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.
How 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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK