Updated for Xcode 14.2
Functions are powerful things in Swift. Yes, you’ve seen how you can call them, pass in values, and return data, but you can also assign them to variables, pass functions into functions, and even return functions from functions.
For example:
func greetUser() {
print("Hi there!")
}
greetUser()
var greetCopy = greetUser
greetCopy()
That creates a trivial function and calls it, but then creates a copy of that function and calls the copy. As a result, it will print the same message twice.
Important: When you’re copying a function, you don’t write the parentheses after it – it’s var greetCopy = greetUser
and not var greetCopy = greetUser()
. If you put the parentheses there you are calling the function and assigning its return value back to something else.
But what if you wanted to skip creating a separate function, and just assign the functionality directly to a constant or variable? Well, it turns out you can do that too:
let sayHello = {
print("Hi there!")
}
sayHello()
Swift gives this the grandiose name closure expression, which is a fancy way of saying we just created a closure – a chunk of code we can pass around and call whenever we want. This one doesn’t have a name, but otherwise it’s effectively a function that takes no parameters and doesn’t return a value.
If you want the closure to accept parameters, they need to be written in a special way. You see, the closure starts and ends with the braces, which means we can’t put code outside those braces to control parameters or return value. So, Swift has a neat workaround: we can put that same information inside the braces, like this:
let sayHello = { (name: String) -> String in
"Hi \(name)!"
}
I added an extra keyword there – did you spot it? It’s the in
keyword, and it comes directly after the parameters and return type of the closure. Again, with a regular function the parameters and return type would come outside the braces, but we can’t do that with closures. So, in
is used to mark the end of the parameters and return type – everything after that is the body of the closure itself. There’s a reason for this, and you’ll see it for yourself soon enough.
In the meantime, you might have a more fundamental question: “why would I ever need these things?” I know, closures do seem awfully obscure. Worse, they seem obscure and complicated – many, many people really struggle with closures when they first meet them, because they are complex beasts and seem like they are never going to be useful.
However, as you’ll see this gets used extensively in Swift, and almost everywhere in SwiftUI. Seriously, you’ll use them in every SwiftUI app you write, sometimes hundreds of times – maybe not necessarily in the form you see above, but you’re going to be using it a lot.
To get an idea of why closures are so useful, I first want to introduce you to function types. You’ve seen how integers have the type Int
, and decimals have the type Double
, etc, and now I want you to think about how functions have types too.
Let’s take the greetUser()
function we wrote earlier: it accepts no parameters, returns no value, and does not throw errors. If we were to write that as a type annotation for greetCopy
, we’d write this:
var greetCopy: () -> Void = greetUser
Let’s break that down…
Void
means “nothing” – this function returns nothing. Sometimes you might see this written as ()
, but we usually avoid that because it can be confused with the empty parameter list.Every function’s type depends on the data it receives and sends back. That might sound simple, but it hides an important catch: the names of the data it receives are not part of the function’s type.
We can demonstrate this with some more code:
func getUserData(for id: Int) -> String {
if id == 1989 {
return "Taylor Swift"
} else {
return "Anonymous"
}
}
let data: (Int) -> String = getUserData
let user = data(1989)
print(user)
That starts off easily enough: it’s a function that accepts an integer and returns a string. But when we take a copy of the function the type of function doesn’t include the for
external parameter name, so when the copy is called we use data(1989)
rather than data(for: 1989)
.
Cunningly this same rule applies to all closures – you might have noticed that I didn’t actually use the sayHello
closure we wrote earlier, and that’s because I didn’t want to leave you questioning the lack of a parameter name at the call site. Let’s call it now:
sayHello("Taylor")
That uses no parameter name, just like when we copy functions. So, again: external parameter names only matter when we’re calling a function directly, not when we create a closure or when we take a copy of the function first.
You’re probably still wondering why all this matters, and it’s all about to become clear. Do you remember I said we can use sorted()
with an array to have it sort its elements?
It means we can write code like this:
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let sortedTeam = team.sorted()
print(sortedTeam)
That’s really neat, but what if we wanted to control that sort – what if we always wanted one person to come first because they were the team captain, with the rest being sorted alphabetically?
Well, sorted()
actually allows us to pass in a custom sorting function to control exactly that. This function must accept two strings, and return true if the first string should be sorted before the second, or false if the first string should be sorted after the second.
If Suzanne were the captain, the function would look like this:
func captainFirstSorted(name1: String, name2: String) -> Bool {
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
}
So, if the first name is Suzanne, return true so that name1
is sorted before name2
. On the other hand, if name2
is Suzanne, return false so that name1
is sorted after name2
. If neither name is Suzanne, just use <
to do a normal alphabetical sort.
Like I said, sorted()
can be passed a function to create a custom sort order, and as long as that function a) accepts two strings, and b) returns a Boolean, sorted()
can use it.
That’s exactly what our new captainFirstSorted()
function does, so we can use it straight away:
let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)
When that runs it will print ["Suzanne", "Gloria", "Piper", "Tasha", "Tiffany"]
, exactly as we wanted.
We’ve now covered two seemingly very different things. First, we can create closures as anonymous functions, storing them inside constants and variables:
let sayHello = {
print("Hi there!")
}
sayHello()
And we’re also able to pass functions into other functions, just like we passed captainFirstSorted()
into sorted()
:
let captainFirstTeam = team.sorted(by: captainFirstSorted)
The power of closures is that we can put these two together: sorted()
wants a function that will accept two strings and return a Boolean, and it doesn’t care if that function was created formally using func
or whether it’s provided using a closure.
So, we could call sorted()
again, but rather than passing in the captainFirstTeam()
function, instead start a new closure: write an open brace, list its parameters and return type, write in
, then put our standard function code.
This is going to hurt your brain at first. It’s not because you’re not smart enough to understand closures or not cut out for Swift programming, only that closures are really hard. Don’t worry – we’re going to look at ways to make this easier!
Okay, let’s write some new code that calls sorted()
using a closure:
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
})
That’s a big chunk of syntax all at once, and again I want to say it’s going to get easier – in the very next chapter we’re going to look at ways to reduce the amount of code so it’s easier to see what’s going on.
But first I want to break down what’s happening there:
sorted()
function as before.by:
down to the closing brace on the last line is part of the closure.sorted()
will pass us, which are two strings. We also say that our closure will return a Boolean, then mark the start of the closure’s code by using in
.Again, there’s a lot of syntax in there and I wouldn’t blame you if you felt a headache coming on, but I hope you can see the benefit of closures at least a little: functions like sorted()
let us pass in custom code to adjust how they work, and do so directly – we don’t need to write out a new function just for that one usage.
Now you understand what closures are, let’s see if we can make them easier to read…
SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.