Waldo sessions now support scripting! – Learn more
App Development

Closures in Swift FAQ: What You Need to Know

Juan Reyes
Juan Reyes
Closures in Swift FAQ: What You Need to Know
May 24, 2022
9
min read

Closures: one of the most versatile and, arguably, misused tools in the programming arsenal. For many, closures are powerful mechanisms for scoped flexibility and DRY coding. For others, however, they are complex nightmares of entangled references and memory gotchas. Nevertheless, it is inarguable that the advent of closures and functional programming has significantly moved the world of programming forward.

If you are not familiar with the concept or if this is the first time you have heard about it, welcome! You are in the right place, particularly if you are a Swift developer, because today we will be taking a journey to explore what Swift closures are and learn how to implement them effectively.

We will start by explaining what closures are exactly and why they are so helpful in the programming sphere. Then, we will explain the difference between a closure and a function while also illustrating when it is best to use a closure instead of a function. Finally, we will explore Swift's different types of closures with some nifty examples to help you grasp how to implement them correctly.

If you are not a Swift developer, we recommend that you check out our other articles on closures.

Let's jump right in.

swift closure pull quote

What Are Closures in Swift?

The concept of closures might seem deceptively complex at first glance, especially when you're exposed to them without much context or a solid grasp of object-oriented programming patterns. However, in reality, the idea behind them is pretty simple and easy to grasp.

As Apple has stated, "closures are self-contained blocks of functionality that can be passed around and used in your code." This definition might sound a bit simplistic and abstract, but it's pretty on point.

A closure is simply a self-contained block of code or "functionality" that behaves like a variable, allowing you to reference it anywhere like any other variable. In short, a closure is a function without the name associated with it.

This definition becomes more apparent once you actually see a closure in code. For example, here's a simple closure that prints the well-known "Hello World" phrase in Swift.


{ (param: type) -> returnType in
  print("Hello World!")
}

As you can see, it basically comes down to being the body of a function.

Notice, however, that the structure is a tad bit different from a basic Swift function. The parameters and returning type lie after the opening bracket and are suffixed by the optional keyword in. These are pretty self-explanatory.

Additionally, you could do away with the parameters and returning-type directives and simply write the function as follows:


{
  print("Hello World!")
}

Simple and clean.

Using Closures

Now, how can we use these closures in a real scenario?

Well, in many ways, really.

You can simply assign the closure to a variable and invoke it just like any function.


var greetMe = { (name: String, age: Int) in
    print("Hi \(name), you are \(age) years old!")
}
greetMe("Juan", 30)

Notice that there are two parameters provided and no returning type since the closure function does not require returning anything.

If you were to run this code in a playground, you would be greeted with the following text:


"Hi Juan, you are 30 years old!"

OK, but why is this different than a function? Clearly, you could create a function with the same number of code lines and have the same result, right?

Well, yes. But that is only because we have yet to explore the real flexibility that comes intrinsically in closures and is not available for a function. More on that later.

swift closure pull quote

Why Do We Need Closures in Swift?

This is a tricky question to answer because, thanks to the flexibility and robustness of the Swift language and its syntax, you could be a proficient and capable developer and never need to use a closure. But this speaks more to the versatility of the language and is not so much about the uselessness of the closure as a tool.

In essence, developing with closures mainly awards you a more readable and maintainable codebase. It also allows you to make your code more DRY and versatile by encapsulating complexity and making it reusable. Finally, Swift has a particular use case for closures that makes the code more compact and streamlined, and that is the trailing closures on function parameters.

Here's an example of a function that requires three parameters: a string, a number, and a closure. Notice how the syntax of the function call is expressed.


func isOldEnough(name: String, age: Int, onOldEnough: (String) -> (String)) {
    print("Checking if \(name) is old enough...")
    if age > 18 {
        print(onOldEnough(name))
    } else {
        print("\(name) is not old enough yet!")
    }
}
isOldEnough(name: "Juan", age: 30) { name in
    return "Congrats \(name), you are old enough!"
}

So, what is going on here?

First, the function stays pretty consistent with the standard syntax, except that the last parameter indicates the closure syntax on its type directive.

Then, as you can see, the closure is invoked only when the specified condition is met.

Finally, the function call is below, but a closure now occupies where the last parameter would reside. This closure merely contains a few instructions that the function will execute according to its conditions.

Now, could this have been done with a function? Yes, absolutely.


func whenOldEnough(name: String) -> String {
    return "Congrats \(name), you are old enough!"
}
func isOldEnough(name: String, age: Int, onOldEnough: (String) -> (String)) {
    print("Checking if \(name) is old enough...")
    if age > 18 {
        print(onOldEnough(name))
    } else {
        print("\(name) is not old enough yet!")
    }
}
isOldEnough(name: "Juan", age: 30, onOldEnough: whenOldEnough)

But the former pattern is more compact, more concise, and, arguably, more readable, which is the objective of closures.

What Is the Difference Between a Closure and a Function?

After seeing the examples above, it's hard not to argue that the difference between closures and functions is pretty insignificant. And guess what, you would be right. But that doesn't mean that they don't behave differently.

A global function, which is the basic function syntax you have seen, does not capture values. And by capturing values, I mean keeping a reference to objects referenced inside it that lie outside its scope. However, a closure does.

Let's illustrate.

A Simple Example

If you were to create a function like the following—which contains a closure inside of it, executes some operations, and returns a reference of said closure—you would get the resulting behavior:


func getAgeOf(name: String) -> (() -> ()) {
    var age: Int = 10
    let addAge = {
        age += 1
        print("The age of \(name) is now \(age)")
    }
    return addAge
}
var test = getAgeOf(name: "Juan")
test() // "The age of Juan is now 11"
test() // "The age of Juan is now 12"
test() // "The age of Juan is now 13"
test() // "The age of Juan is now 14"
test() // "The age of Juan is now 15"

Now, I know that this seems like a complex and convoluted example, but what is happening here is quite simple.

The getAgeOf function initializes two variables: an Int for the age and a closure, which it then returns. The closure contains a reference of the age variable, which is initialized as 10 and increases its value by one. It then proceeds to print the current value of the age variable with a message.

What we are doing here is simply "constructing" a closure with some predefined values, which are kept by reference. And so, when you call the getAgeOf function, you get a closure that you can call and invoke as many times as you want.

Now, since the value of the age variable is referenced inside the closure, it is kept in memory and "remembered" for as long as the closure itself is kept in memory. This means that every time we call the closure, it increases the value accordingly.

If you were to try to reproduce this pattern with a global function, it wouldn't work. This is because global functions don't have a way to keep the values of variables outside of their scope.

However, you could reproduce the closure behavior with nested functions, which are essentially named closures.

So, in short:

  • Global functions have a name, and they don't capture values.
  • Nested functions have a name, and they capture referenced values outside of their scope.
  • Closures don't have a name, and they capture referenced values outside of their scope.

When Would You Use Closures Instead of Functions?

The argument of where to use closures instead of functions comes down to preferences. This is mainly due to the versatility and robustness of the Swift language.

Given that, for the most part, there's very little incentive outside of strict development patterns and guidelines on teams and organizations, you could argue that the only reasons are what you are more comfortable with or what makes you more productive.

For the most part, the community seems to agree that closures are practical tools for reducing the apparent complexity of code and making life easier in the long run when you need to maintain projects.

Moreover, it seems pretty clear that closures allowing the capture of values outside of their scope might incentivize more bugs, given that some programmers might not be able to handle references properly. And this is a perfectly valid point. However, this issue is mostly mitigated by the platform's ability to manage memory issues.

So, in our opinion, as long as you know what you are doing by referencing a variable that's initialized outside of the closure, you should be fine.

swift closure pull quote

What's Next?

Like all other programming patterns available in Swift, closures are just a tool. And like all tools, they are as powerful or as dangerous as the capacity of the user.

Thankfully, the Swift programming language has come a long way from Obj-C blocks and the nightmare of memory leaks.

If you want to ensure that your project is bug-free and ready for primetime, you need to have a solid testing workflow. Sometimes, however, building this workflow can be expensive and time-consuming, especially for lean and specialized teams.

We recommend that you consider using Waldo's code-free testing workflow solution. You won't need to hire more engineers or take any of their productive time with it. Just set it up, and it's ready to go. Learn more about Swift framework support here.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.