UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to create and use closures

Paul Hudson    @twostraws   

Updated for Xcode 15

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…

  1. The empty parentheses marks a function that takes no parameters.
  2. The arrow means just what it means when creating a function: we’re about to declare the return type for the function.
  3. 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:

  1. We’re calling the sorted() function as before.
  2. Rather than passing in a function, we’re passing a closure – everything from the opening brace after by: down to the closing brace on the last line is part of the closure.
  3. Directly inside the closure we list the two parameters 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.
  4. Everything else is just normal function code.

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…

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.7/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.