3

7 ways to pass a closure as an argument in Swift

 2 years ago
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

20 Sep 2021 ⋅ 6 min read ⋅ Swift Closure

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.

sponsor-codeshot.png

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.

  1. I will use the filter method to get an array containing only an integer greater than 5.
  2. 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.

sponsor-codeshot.png

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

What is @escaping in Swift closures

Learn the meaning of @escaping, so you know what to do when you see it or when you need to write one.

Swift Closure
Codable in Swift 4.0

Can it replace JSON encode/decode lib out there?

Swift
Sign in with Apple Tutorial, Part 3: Backend – Token verification

Part 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.

Swift

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

Share

Previous
How to make a custom button style with UIButton.Configuration in iOS 15

Learn how to create an outline button style.

Next
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.

← Home


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK