If you've worked with blocks in C/Objective-C or lambdas in Ruby, then you won't have a hard time wrapping your head around the concept of closures. Closures are nothing more than blocks of functionality that you can pass around in your code.
As a matter of fact, we've already worked with closures in the previous two articles. That's right. Functions are closures too. Let's start with the basics and inspect the anatomy of a closure.
1. What Is a Closure?
As I said, a closure is a block of functionality that you can pass around in your code. You can pass a closure as an argument of a function or you can store it as a property of an object. Closures have many use cases.
The name closure hints at one of the key characteristics of closures. A closure captures the variables and constants of the context in which it is defined. This is sometimes referred to as closing over those variables and constants. We're going to look at value capturing in more detail at the end of this article.
Flexibility
You've already learned that functions can be incredibly powerful and flexible. Because functions are closures, closures are just as flexible. In this article, you'll discover just how flexible and powerful they are.
Memory Management
The C programming language has a similar concept, blocks. Closures in Swift, however, have a few benefits. One of the key advantages of closures in Swift is that memory management is something you, the developer, don't have to worry about.
Even retain cycles, which aren't uncommon in C/Objective-C, are handled by Swift. This will reduce hard-to-find memory leaks or crashes due to invalid pointers.
2. Syntax
The basic syntax of a closure isn't difficult and it will remind you of global and nested functions, which we covered earlier in this series. Take a look at the following example.
{(a: Int) -> Int in return a + 1 }
The first thing you'll notice is that the entire closure is wrapped in a pair of curly braces. Closure's parameters are wrapped in a pair of parentheses, separated from the return type by the ->
symbol. The above closure accepts one argument, a
, of type Int
and returns an Int
. The body of the closure starts after the in
keyword.
Named closures, that is, global and nested functions, look a bit different. The following example should illustrate the differences.
func increment(a: Int) -> Int { return a + 1 }
The most prominent differences are the use of the func
keyword and the position of the parameters and return type. A closure starts and ends with a curly brace, wrapping the parameters, return type, and closure body. Despite these differences, remember that every function is a closure. Not every closure is a function though.
3. Closures as Parameters
Closures are powerful and the following example illustrates how useful they can be. In the example, we create an array of states. We invoke the map
function on the array to create a new array that only contains the first two letters of each state as a capitalized string.
var states = ["California", "New York", "Texas", "Alaska"] let abbreviatedStates = states.map({ (state: String) -> String in return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString }) println(abbreviatedStates)
The map
function or method is common in many programming languages and libraries, such as Ruby, PHP, and jQuery. In the above example, the map
function is invoked on the states
array, transforms its contents, and returns a new array that contains the transformed values.
Type Inference
Previously in this series, we learned that Swift is quite smart. Let's see exactly how smart. The array of states is an array of strings. Because we invoke the map
function on the array, Swift knows that the state
argument is of type String
. This means that we can omit the type as shown in the updated example below.
let abbreviations = states.map({ (state) -> String in return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString })
There are a few more things we can omit from the above example, resulting in the following one-liner.
let abbreviations = states.map({ state in state.substringToIndex(advance(state.startIndex, 2)).uppercaseString })
Let me explain what's happening. The compiler can infer that we return a string from the closure that we pass to the map
function, which means there's no reason to include it in the closure expression definition. We can only do this if the closure's body includes a single statement though. In that case, we can put that statement on the same line as the closure's definition, as shown in the above example. Because there's no return type in the definition and no ->
symbol preceding the return type, we can omit the parentheses enclosing the closure's parameters.
Shorthand Argument Names
It doesn't stop here though. We can make use of shorthand argument names to simplify the above closure expression even more. When using an inline closure expression, as in the above example, we can omit the list of parameters, including the in
keyword that separates the parameters from the closure body.
In the closure body, we reference the arguments using shorthand argument names Swift provides us with. The first argument is referenced by $0
, the second by $1
, etc.
In the updated example below, I have omitted the list of parameters and the in
keyword, and replaced the state
argument in the closure's body with the shorthand argument name $0
. The resulting statement is more concise without compromising readability.
let abbreviations = states.map({ $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString })
Trailing Closures
The Swift programming language also defines a concept known as trailing closures. The idea is simple. If you pass a closure as the last argument of a function, you can place that closure outside the parentheses of the function call. The following example demonstrates how this works.
let abbreviations = states.map() { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
If the only argument of the function call is the closure, then it's even possible to omit the parentheses of the function call.
let abbreviations = states.map { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
Note that this also works for closures that contain multiple statements. In fact, that is the main reason trailing closures are available in Swift. If a closure is long or complex, and it's the last argument of a function, it is often better to use the trailing closure syntax.
let abbreviations = states.map { (state: String) -> String in let newState = state.substringToIndex(advance(state.startIndex, 2)) return newState.uppercaseString }
4. Capturing Values
When using closures, you'll often find yourself using or manipulating constants and variables from the closure's surrounding context in the body of the closure. This is possible and often referred to as value capturing. It simply means that a closure can capture the values of constants and variables from the context it is defined in. Take the following example to better understand the concept of value capturing.
func changeCase(uppercase: Bool, strings: String...) -> [String] { var newStrings = [String]() func changeToUppercase() { for s in strings { newStrings.append(s.uppercaseString) } } func changeToLowerCase() { for s in strings { newStrings.append(s.lowercaseString) } } if uppercase { changeToUppercase() } else { changeToLowerCase() } return newStrings } let uppercaseStates = changeCase(true, "Califorina", "New York") let lowercaseStates = changeCase(false, "Califorina", "New York")
I'm sure you agree the above example is a bit contrived, but it clearly shows how value capturing works in Swift. The nested functions, changeToUppercase
and changeToLowercase
, have access to the outer function's arguments, states
, as well as the newStates
variable declared in the outer function. Let me explain what happens.
The changeCase
function accepts a boolean as its first argument and a variadic parameter of type String
as its second parameter. The function returns an array of strings composed of the strings passed to the function as the second argument. In the function's body, we create a mutable array of strings, newStrings
, in which we store the manipulated strings.
The nested functions loop over the strings that are passed to the changeCase
function and change the case of each string. As you can see, they have direct access to the strings passed to the changeCase
function as well as the newStrings
array, which is declared in the body of the changeCase
function.
We check the value of uppercase
, call the appropriate function, and return the newStrings
array. The two lines at the end of the example demonstrate how the changeCase
function works.
Even though I've demonstrated value capturing with functions, remember that every function is a closure. In other words, the same rules apply to unnamed closures.
Closures
It's been mentioned several times in this article, functions are closures. There are three kinds of closures:
- global functions
- nested functions
- closure expressions
Global functions, such as the println
function of Swift's standard library, capture no values. Nested functions, however, have access to and can capture the values of constants and values of the function they are defined in. The previous example illustrates this concept.
Closure expressions, also known as unnamed closures, can capture the values of constants and variables of the context they are defined in. This is very similar to nested functions.
Copying and Referencing
A closure that captures the value of a variable is able to change the value of that variable. Swift is clever enough to know whether it should copy or reference the values of the constants and variables it captures.
Developers that are new to Swift and have little experience with other programming languages will take this behavior for granted. However, it's an important advantage that Swift understands how captured values are being used in a closure and, as a result, can handle memory management for us.
Learn More in Our Swift Programming Course
If you're interested in taking your Swift education to the next level, you can take a look at our full course on Swift development.
Conclusion
Closures are an important concept and you will use them often in Swift. They enable you to write flexible, dynamic code that is both easy to write and understand. In the next article, we will explore object oriented programming in Swift, starting with objects, structures, and classes.