What a closure is
A closure is a function without a name that you write inline, between braces, and pass to another function. Sorting is the classic use. The method sorted(by:) does not decide the order itself. You give it a closure that takes two elements and returns true when the first should come before the second.
let nums = [3, 1, 2]
let small = nums.sorted(by: { (a: Int, b: Int) -> Bool in
return a < b
})
print(small)
[1, 2, 3]
In the closure, (a: Int, b: Int) -> Bool lists the parameters and the return type, and in separates that header from the body.
Shortening the closure
Swift lets you drop most of that in three steps:
- Leave out the types. The compiler knows
sorted(by:)compares two elements and returns aBool. - Leave out
return. A closure with a single expression returns that expression. - Use
$0and$1. These automatic names stand for the first and second parameter, so the header can go as well.
let small = nums.sorted(by: { $0 < $1 })
Trailing closure syntax
When the closure is the last argument, you can write it after the parentheses. Here the call has no other arguments, so the parentheses disappear too:
let big = nums.sorted { $0 > $1 }
print(big)
[3, 2, 1]
The rule inside the closure can be anything that gives a yes or no answer for two elements. { $0.count > $1.count } puts longer strings first, because count is the length of a string.
sorted() or sort()
Swift has two sorting methods:
sorted()andsorted(by:)return a new array and leave the original untouched. They work on aletconstant.sort()andsort(by:)rearrange the array in place, so the array must be avar.
Calling sort() on a constant is a compile error:
let names = ["Charlie", "Al"]
names.sort()
error: cannot use mutating member on immutable value: 'names' is a 'let' constant
"Mutating member" means the method modifies the value it is called on. Either switch to sorted(), which is usually what you want, or declare the array with var.
The exercise, step by step
- Create
namesas aletarray of four strings. A constant is enough, becausesortednever changes it. - Sort alphabetically into
ascendingwithsorted(by: { $0 < $1 }). Strings compare in alphabetical order with<, so"Alexandra"comes first and"Kim"last. - Print
ascendingwith string interpolation,\(...), which inserts any value, an array included, into the string. - Sort by length into
byLengthwith a trailing closure that returnstruewhen the first string is longer. - Print
byLengththe same way.
let names = ["Bo", "Alexandra", "Kim", "Charlie"]
let ascending = names.sorted(by: { $0 < $1 })
print("ascending = \(ascending)")
let byLength = names.sorted { $0.count > $1.count }
print("byLength = \(byLength)")
ascending = ["Alexandra", "Bo", "Charlie", "Kim"]
byLength = ["Alexandra", "Charlie", "Kim", "Bo"]
Both results come from the same names array, which keeps its original order. The only difference between them is the closure.
Common mistakes
- Using
sort()on alet. Usesorted()or avar. - Ignoring the result of
sorted. It returns the new array. Writingnames.sorted()on its own line changes nothing. - Getting the direction backwards. The closure answers "should the first come before the second?".
<gives ascending order and>gives descending. - Expecting the shortest first.
$0.count > $1.countputs the longest first. Use<for the opposite.
Key takeaways
- A closure is an unnamed function you pass as an argument.
sorted(by:)returns a new array ordered by your rule, andsort(by:)reorders avararray in place.$0and$1name the closure's parameters, and a last-argument closure can go outside the parentheses.
