Swift Closures
Closures are self-contained blocks of functionality that can be used within your code or passed as arguments.
Swift closures are similar to blocks in C and Objective-C, as well as anonymous functions in other programming languages.
Global functions and nested functions are actually special cases of closures.
Closure forms include:
Global Function | Nested Function | Closure Expression |
---|---|---|
Has a name but cannot capture any values. | Has a name and can capture values within the enclosing function. | Anonymous closure, uses lightweight syntax, and can capture values based on the context. |
Swift closures offer many optimizations:
- Inferring parameter and return value types from context
- Implicit return from single-expression closures (i.e., closures with a single line of code can omit the
return
keyword) - Using shorthand argument names like
$0
,$1
(starting from 0, representing the ith argument) - Providing trailing closure syntax
Syntax
The following defines a closure syntax that accepts parameters and returns a specified type:
{(parameters) -> return type in
statements
}
Example
import Cocoa
let studname = { print("Swift closure example.") }
studname()
The output of the above program is:
Swift closure example.
The following closure form accepts two parameters and returns a Boolean value:
{(Int, Int) -> Bool in
Statement1
Statement 2
---
Statement n
}
Example
import Cocoa
let divide = {(val1: Int, val2: Int) -> Int in
return val1 / val2
}
let result = divide(200, 20)
print (result)
The output of the above program is:
10
Closure Expressions
Closure expressions are a concise way to construct inline closures. They provide several syntactic optimizations, making closures easy to write.
sorted Method
Swift's standard library provides a method called sorted(by:), which sorts the values of an array based on the sorting closure function you provide.
After sorting, the sorted(by:) method returns a new array of the same size, containing the same type of elements, but in sorted order. The original array is not modified by the sorted(by:) method.
The sorted(by:) method requires two parameters:
- An array of known type
- A closure function that takes two values of the same type as the array elements and returns a Boolean value indicating whether the first parameter should appear before the second parameter after sorting. If the first parameter should come before the second, the sorting closure function returns true, otherwise it returns false.
Example
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
// Using a regular function (or nested function) to provide sorting functionality, the closure function type needs to be (String, String) -> Bool.
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sorted(by: backwards)
print(reversed)
The output of the above program is:
["S", "D", "BE", "AT", "AE"]
If the first string (s1) is greater than the second string (s2), the backwards
function returns true
, indicating that in the new array, s1 should appear before s2. For characters in strings, "greater" means "appears later in the alphabet." This means that "B" is greater than "A", and "S" is greater than "D". It sorts the strings in reverse alphabetical order, with "AT" coming before "AE".
Parameter Name Abbreviations
Swift automatically provides shorthand argument names for inline closures, allowing you to refer to the parameters as $0
, $1
, $2
, and so on.
Example
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted( by: { $0 > $1 } )
print(reversed)
$0
and $1
represent the first and second String
type parameters in the closure.
The output of the above program is:
["S", "D", "BE", "AT", "AE"]
If you use shorthand argument names in the closure expression, you can omit their definition in the parameter list, and the type of the shorthand argument names will be inferred from the function type. The in
keyword can also be omitted.
Operator Functions
Swift provides operator functions that can be used as closures. There is actually a shorter way to write the closure expression in the example above.
Swift's String
type defines a string implementation for the greater-than operator (>
), which takes two String
type parameters and returns a Bool
type value. This matches the function type required by the second parameter of the sort(_:)
method. Therefore, you can simply pass a greater-than operator, and Swift can automatically infer that you want to use the string implementation of the greater-than function:
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted(by: >)
print(reversed)
The output of the above program is:
["S", "D", "BE", "AT", "AE"]
Trailing Closures
A trailing closure is a closure expression written after the function call's parentheses, which the function supports as the last argument.
func someFunctionThatTakesAClosure(closure: () -> Void) {
// Function body part
}
// The following is a function call without using a trailing closure
someFunctionThatTakesAClosure({
// Closure body part
})
// The following is a function call using a trailing closure
someFunctionThatTakesAClosure() {
// Closure body part
}
Example
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
// Trailing closure
var reversed = names.sorted() { $0 > $1 }
print(reversed)
{ $0 > $1 }
after sort()
is a trailing closure.
The output of the above program is:
["S", "D", "BE", "AT", "AE"]
>
Note:
If the function only needs one closure expression as a parameter, you can even omit the ()
when using a trailing closure.
reversed = names.sorted { $0 > $1 }
Capturing Values
A closure can capture constants or variables from its defining context.
Even if the original scope that defined these constants and variables no longer exists, the closure can still reference and modify these values within the closure's function body.
The simplest form of a closure in Swift is a nested function, which is a function defined within another function's body.
A nested function can capture all the parameters of its outer function and any constants and variables defined within the outer function.
Consider this example:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
The function makeIncrementor
has an Int
type parameter amount
with an external parameter name forIncrement
, meaning you must use this external name when calling the function. It returns a function of type () -> Int
.
Inside the function body, a variable runningTotal
and a function incrementor
are declared.
The incrementor
function does not take any parameters, but it accesses the runningTotal
and amount
variables within its body. This is achieved by capturing the existing runningTotal
and amount
variables from its enclosing function.
Since amount
is not modified, incrementor
actually captures and stores a copy of the variable, which is stored along with the incrementor
function.
Thus, when we call this function, it accumulates:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// Returns 10
print(incrementByTen())
// Returns 20
print(incrementByTen())
// Returns 30
print(incrementByTen())
The output of the above program is:
10
20
30
Closures Are Reference Types
Closures are reference types in Swift. This means that when you assign a closure to multiple variables, they all refer to the same closure. In the example above, incrementByTen is a constant, but the closure it points to can still increment the value of the variable it captures.
This is because functions and closures are reference types.
Whether you assign a function/closure to a constant or a variable, you are actually setting the value of the constant/variable to a reference to the corresponding function/closure. In the example above, incrementByTen is a constant that points to a reference of the closure, not the closure itself.
This also means that if you assign a closure to two different constants/variables, both values will point to the same closure:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// Returns a value of 10
incrementByTen()
// Returns a value of 20
incrementByTen()
// Returns a value of 30
incrementByTen()
// Returns a value of 40
incrementByTen()
let alsoIncrementByTen = incrementByTen
// Returns a value of 50
print(alsoIncrementByTen())
The output of the above program is:
50