English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Swift Closures

In this article, you will learn about closures through examples, including their syntax and types of closures in Swift.

In the article on Swift functions, we created a function using the func keyword. However, Swift also has another special type of function called a closure, which can be defined without using the func keyword and a function name.
Like functions, closures can accept parameters and return values. They also contain a set of statements that are executed after calling and can be assigned to variables as functions./Constants.

Closures in Swift are similar to those in C and Objective-C.-Code blocks in C and anonymous functions in other programming languages are quite similar.

Global functions and nested functions are actually special closures.

The forms of closures are:

Global functionNested functionClosure expression
Named but cannot capture any values.Named, and can also capture values within the enclosed function.Unnamed closure, uses lightweight syntax, and can capture values according to the context environment.

Swift has many optimizations for closures:

  • Infer parameter and return value types based on context

  • Implicitly return from a single-line expression closure (that is, the closure body only has one line of code, and return can be omitted)

  • You can use simplified parameter names, such as $0, $1(Starting from 0, representing the i-th parameter...)

  • Provides trailing closure syntax (Trailing closure syntax)

Syntax

The following defines a closure syntax that receives parameters and returns a specified type:

{(parameters) -> return type in
   statements
}

Online examples

let simpleClosure = {
    print("Hello, World!")
}
simpleClosure()

The output result of the above program is:

Hello, World!

The following closure form receives two parameters and returns a Boolean value:

{(Int, Int) -> Bool in
   Statement1
   Statement 2
    ---
   Statement n
}

Online examples

let simpleClosure:(String) -> (String) = { name in
    
    let greeting = "Hello, World!" + "Program"
    return greeting
}
let result = simpleClosure("Hello, World")
print(result)

The output result of the above program is:

Hello, World! Program

Closure expression

Closure expression is a way to construct inline closures using concise syntax. Closure expressions provide some syntax optimizations that make writing closures simple and clear.

sorted method

Swift's standard library provides a function named sorted(by:) The method, will sort the values in the known type array according to the closure function you provide for sorting.

After sorting is completed, the sorted(by:) method will return a new array of the same size as the original array, containing elements of the same type and elements that have been correctly sorted. The original array will not be modified by the sorted(by:) method.

The sorted(by:) method requires two parameters to be passed in:

  • An array of known types

  • Closure function, which requires passing two values of the same type as the array elements and returns a Boolean value to indicate whether the first parameter passed in is in front of or behind the second parameter after sorting. If the value of the first parameter appears before the value of the second parameter, the sorting closure function needs to return true, on the contrary, returns false.

Online examples

import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
// Provide sorting functionality using a regular function (or an embedded function), and the closure function type must be (String, String) -> Bool.
func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}
var reversed = names.sorted(by: backwards)
print(reversed)

The output result of the above program is:

["S", "D", "BE", "AT", "AE"]

if the first string (s1) is greater than the second string (s2), backwards function returns true, indicating that s1should appear in s2before. For characters in a string, 'greater than' means 'appears later in alphabetical order'. This means that the letter 'B' is greater than the letter 'A', and the string 'S' is greater than the string 'D'. It will perform reverse alphabetical sorting, and 'AT' will be ranked before 'AE'.

parameter name abbreviation

Swift automatically provides parameter name abbreviation for inline functions, where you can directly use $0, $1,2to call the closure parameters in order.

Online examples

import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted(by: { $0 > $1 })
print(reversed)

$0 and $1represents the first and second String type parameters in the closure.

The output result of the above program is:

["S", "D", "BE", "AT", "AE"]

If you use parameter name abbreviation in a closure expression, you can omit its definition in the closure parameter list, and the type corresponding to the parameter name abbreviation will be inferred through the function type. The 'in' keyword can also be omitted.

operator function

There is actually a shorter way to write the closure expression in the above example.

Swift'sStringType defines information about the greater than sign (>) string implementation, which accepts twoStringtype parameter and returnsBoolvalue of the type. This is exactly the same assort(_):The second parameter of the method requires a function type that matches. Therefore, you can simply pass a greater than sign, and Swift can automatically infer that you want to use the string function implementation of the greater than sign:

import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted(by: >)
print(reversed)

The output result of the above program is:

["S", "D", "BE", "AT", "AE"]

Trailing closure

A trailing closure is a closure expression written after the function parentheses, and the function supports calling it as the last parameter.

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // Function body part
}
// The following is a function call without using trailing closure
someFunctionThatTakesAClosure({
    // Closure body
)
// This is a function call using trailing closure
someFunctionThatTakesAClosure() {}}
  // Closure body
}

Online examples

import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
//Trailing closure
var reversed = names.sorted() { $0 > $1 }
print(reversed)

After sort() { $0 > $1} as a trailing closure.

The output result of the above program is:

["S", "D", "BE", "AT", "AE"]

Note: If the function only needs one parameter from the closure expression, when you use a trailing closure, you can even omit()Omitted.

reversed = names.sorted { $0 > $1 }

Captured values

Closures can capture constants or variables in their defining context.

Even if the original scope of defining these constants and variables does not exist, closures can still refer to and modify these values within the closure function body.

The simplest form of closure in Swift is the nested function, which is a function defined within the body of another function.

Nested functions can capture all the parameters and defined constants and variables of the outer function.

See this example:

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

A function makeIncrementor, which has an Int-type parameter amout, and it has an external parameter named forIncremet, meaning that you must use this external name when you call it. The return value is a()-> Intfunction.

In the function body, the 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 the function body. This is because it achieves this by capturing the runningTotal and amount variables that already exist within the function that contains it.

Since the amount variable was not modified, incrementor actually captures and stores a copy of the variable, and this copy is stored along with incrementor.

So when we call this function, it will accumulate:

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)
// The returned value is10
print(incrementByTen())
// The returned value is20
print(incrementByTen())
// The returned value is30
print(incrementByTen())

The output result of the above program is:

10
20
30

Closures are reference types

In the above example, incrementByTen is a constant, but the closures pointed to by these constants can still increase the value of the captured variables.

This is because both functions and closures are reference types.

No matter how you will function/Whether you assign a closure to a constant or a variable, you are actually assigning a constant/The value of the variable is set to the corresponding function/The reference of closure. In the above example, the reference of incrementByTen to the closure is a constant, not the closure content 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)
// The returned value is10
incrementByTen()
// The returned value is20
incrementByTen()
// The returned value is30
incrementByTen()
// The returned value is40
incrementByTen()
let alsoIncrementByTen = incrementByTen
// The returned value is also50
print(alsoIncrementByTen())

The output result of the above program is:

50