7 ways to pass a closure as an argument in Swift

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

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

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

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.

Sponsor sarunw.com and reach thousands of iOS developers.

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