©Shutterstock/NDAB Creativity
Swift offers various ways to optimize closures so that they’re brief and succinct. The various optimizations include the following:
- Inferring parameter types and return types
- Implicit returns from single-statement closures
- Shorthand argument names
- Trailing closure syntax
- Operator closure
Understanding Swift closures
The best way to understand Swift closures is to use an example. Suppose you have the following array of integers:let numbers = [5,2,8,7,9,4,3,1]Assume you want to sort this array in ascending order. You could write your own function to perform the sorting, or you could use the
sorted()
function available in Swift. The sorted()
function takes two arguments:
- An array to be sorted
- A closure that takes two arguments of the same type as the array and returns a true if the first value appears before the second value
Using Swift functions as closures
In Swift, functions are special types of closures. As mentioned, the sorted()
function needs a closure that takes two arguments of the same type as the array, returning a true
if the first value appears before the second value. The following Swift function fulfils that requirement:
func ascending(num1:Int, num2:Int) -> Bool { return num1The
ascending()
function takes two arguments of type Int
and returns a Bool
value. If num1 is less than num2
, it returns true. You can now pass this function to the sorted()
function, as shown here:
var sortedNumbers = numbers.sorted(by: ascending)The
sorted()
function now returns the array that is sorted in ascending order.The sorted()
function does not modify the original array. It returns the sorted array as a new array.
Assigning Swift closures to variables
As mentioned earlier, functions are special types of closures. In fact, a closure is a function without a name. However, you can assign a closure to a variable — for example, the ascending()
function discussed earlier can be written as a closure assigned to a variable:
var compareClosure : (Int, Int)->Bool = { (num1:Int, num2:Int) -> Bool in return num1 < num2 }To use the
compareClosure
closure with the sorted()
function, pass in the compareClosure variable:
sortedNumbers = numbers.sorted(by: <strong>compareClosure</strong>)
Writing Swift closures inline
You can pass a function into thesorted()
function as a closure function, but a better way is to write the closure inline, which obviates the need to define a function explicitly or assign it to a variable.Rewriting the earlier example would yield the following:
sortedNumbers = numbers.sorted(by: { (num1:Int, num2:Int) -> Bool in return num1 < num2 } )As you can see, the
ascending()
function name is now gone; all you’ve supplied is the parameter list and the content of the function.If you want to sort the array in descending order, you can simply change the comparison operator:
sortedNumbers = numbers.sorted(by: { (num1:Int, num2:Int) -> Bool in return num1 > num2 } )
Understanding type inference
Because the type of the first argument of the closure function must be the same as the type of array you’re sorting, it’s actually redundant to specify the type in the closure, because the compiler can infer that from the type of array you’re using:var fruits = ["orange", "apple", "durian", "rambutan", "pineapple"] print(fruits.sorted(by: { (fruit1, fruit2) in return fruit1If your closure has only a single statement, you can even omit the
return
keyword:
print(fruits.sorted(by: { (fruit1, fruit2) in fruit1
Using shorthand argument names
Above, names were given to arguments within a closure. In fact, this is also optional, because Swift automatically provides shorthand names to the parameters, which you can refer to as$0
, $1
, and so on.The previous code snippet could be rewritten as follows without using named parameters:
print(fruits.sorted(by: { $0<$1 }) )To make the closure really terse, you can write everything on one line:
print(fruits.sorted(by:{ $0<$1 }))
Working with Swift’s operator function
You saw that the closure for the sorted() function was reduced to the following:print(fruits.sorted(by:{ $0<$1 }))One of the implementations of the lesser than (
<
) operator is actually a function that works with two operands of type String
. Because of this, you can actually simply specify the <
operator in place of the closure, and the compiler will automatically infer that you want to use the particular implementation of the
< operator. The preceding statement can be reduced to the following:
print(fruits.sorted(by:<strong><</strong>))If you want to sort the array in descending order, simply use the greater than (>) operator:
print(fruits.sorted(by:<strong>></strong>))
Using trailing closures in Swift
Consider the closure that you saw earlier:print(fruits.sorted(by: { (fruit1, fruit2) in return fruit1Notice that the closure is passed in as a second argument of the
sorted()
function. For long closures, this syntax may be a little messy. If the closure is the final argument of a function, you can rewrite this closure as a trailing closure. A trailing closure is written outside of the parentheses of the function call. The preceding code snippet, when rewritten using the trailing closure, looks like this:
print(fruits.sorted() { (fruit1, fruit2) in return fruit1Using the shorthand argument name, the closure can be shortened to the following:
print(fruits.sorted()<strong>{$0<$1}</strong>)
Want to learn more? Check out our SwiftUI Cheat Sheet.