Spec-Zone.ru › Swift Language

Замыкания

Группа кода, выполняющегося вместе, без создания именованной функции.

Замыкания — это самодостаточные блоки функциональности, которые можно передавать и использовать в вашем коде. Замыкания в Swift аналогичны замыканиям, анонимным функциям, лямбдам и блокам в других языках программирования.

Замыкания могут захватывать и хранить ссылки на любые константы и переменные из контекста, в котором они определены. Это называется захватом этих констант и переменных. Swift обрабатывает все управление памятью при захвате за вас.

Примечание: не беспокойтесь, если вы не знакомы с концепцией захвата. Она подробно описана ниже в <doc:Closures#Capturing-Values>.

Глобальные и вложенные функции, как введено в Функции, на самом деле являются особыми случаями замыканий. Замыкания принимают одну из трёх форм:

  • Глобальные функции — это замыкания, имеющие имя и не захватывающие никаких значений.
  • Вложенные функции — это замыкания, имеющие имя и способные захватывать значения из своей окружающей функции.
  • Выражения замыканий — это безымянные замыкания, написанные в лёгкой синтаксической форме, которые могут захватывать значения из окружающего контекста.

Выражения замыканий Swift имеют чистый и понятный стиль с оптимизациями, которые поощряют краткий и понятный синтаксис в распространённых сценариях. Эти оптимизации включают:

  • Вывод типов параметров и возвращаемого значения из контекста
  • Неявное возвращение из замыканий с одной строкой кода
  • Сокращённые имена аргументов
  • Синтаксис замыкания в конце

Выражения замыканий

Вложенные функции, как введено в <doc:Functions#Nested-Functions>, являются удобным средством именования и определения самодостаточных блоков кода как части большей функции. Однако иногда полезно писать более короткие версии функциональных конструкций без полного объявления и имени. Это особенно актуально, когда вы работаете с функциями или методами, которые принимают функции как один или несколько из своих аргументов.

Выражения замыканий — это способ написания встроенных замыканий с кратким, сфокусированным синтаксисом. Выражения замыканий предоставляют несколько синтаксических оптимизаций для написания замыканий в сокращённой форме без потери ясности или намерений. Примеры выражений замыканий ниже иллюстрируют эти оптимизации, уточняя единственный пример метода sorted(by:) на протяжении нескольких итераций, каждая из которых выражает ту же функциональность более лаконичным способом.

Метод Sorted

Стандартная библиотека Swift предоставляет метод, называемый sorted(by:), который сортирует массив значений известного типа на основе вывода сортирующего замыкания, которое вы предоставляете. По завершении процесса сортировки метод sorted(by:) возвращает новый массив того же типа и размера, что и старый, с элементами в правильном отсортированном порядке. Исходный массив не изменяется методом sorted(by:).

Примеры выражений замыканий ниже используют метод sorted(by:) для сортировки массива значений String в обратном алфавитном порядке. Вот исходный массив для сортировки:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

Метод sorted(by:) принимает замыкание, которое принимает два аргумента того же типа, что и содержимое массива, и возвращает значение Bool, чтобы указать, следует ли первый элемент появляться перед или после второго элемента после сортировки. Сортирующее замыкание должно возвращать true, если первый элемент должен появляться перед вторым, и false в противном случае.

Этот пример сортирует массив значений String, и поэтому сортирующее замыкание должно быть функцией типа (String, String) -> Bool.

Один из способов предоставления сортирующего замыкания — написать обычную функцию нужного типа и передать её в качестве аргумента методу sorted(by:):

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Если первая строка (s1) больше второй строки (s2), функция backward(_:_:) вернёт true, указывая, что s1 должно появиться перед s2 в отсортированном массиве. Для символов в строках «больше» означает «появляется позже в алфавите, чем». Это означает, что буква "B" «больше», чем буква "A", а строка "Tom" больше, чем строка "Tim". Это даёт сортировку в обратном алфавитном порядке, где "Barry" будет размещено перед "Alex" и так далее.

Однако это довольно длинный способ написать то, что по сути является однострочной функцией (a > b). В этом примере было бы предпочтительнее написать сортирующее замыкание встроенно, используя синтаксис выражения замыкания.

Синтаксис выражения замыкания

Синтаксис выражения замыкания имеет следующий общий вид:

{ (<#parameters#>) -> <#return type#> in
   <#statements#>
}

Параметры в синтаксисе выражения замыкания могут быть параметрами in-out, но они не могут иметь значение по умолчанию. Можно использовать параметр с переменным числом аргументов, если вы назовёте параметр с переменным числом аргументов. Кортежи также могут использоваться в качестве типов параметров и типов возвращаемых значений.

Пример ниже демонстрирует версию выражения замыкания функции backward(_:_:) из примера выше:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

Обратите внимание, что объявление параметров и типа возвращаемого значения для этого встроенного замыкания идентично объявлению из функции backward(_:_:). В обоих случаях оно записывается как (s1: String, s2: String) -> Bool. Однако для встроенного выражения замыкания параметры и тип возвращаемого значения записываются внутри фигурных скобок, а не вне их.

Начало тела замыкания вводится ключевым словом in. Это ключевое слово указывает на то, что определение параметров и типа возвращаемого значения замыкания завершено, и вот-вот начнётся тело замыкания.

Поскольку тело замыкания настолько короткое, его можно даже записать в одну строку:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

Это иллюстрирует, что общий вызов метода sorted(by:) остался неизменным. Пара скобок всё ещё охватывают весь аргумент метода. Однако этот аргумент теперь является встроенным замыканием.

Вывод типа из контекста

Поскольку сортирующее замыкание передаётся в качестве аргумента методу, Swift может вывести типы его параметров и тип возвращаемого значения. Метод sorted(by:) вызывается с массивом строк, поэтому его аргумент должен быть функцией типа (String, String) -> Bool. Это означает, что типы (String, String) и Bool не нужно писать в определении выражения замыкания. Поскольку все типы могут быть выведены, стрелка возврата (->) и скобки вокруг имён параметров также могут быть опущены:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

Всегда можно вывести типы параметров и тип возвращаемого значения при передаче замыкания в функцию или метод как встроенное выражение замыкания. В результате вам никогда не нужно писать встроенное замыкание в полном виде, когда замыкание используется в качестве аргумента функции или метода.

Тем не менее, вы по-прежнему можете сделать типы явными, если хотите, и это рекомендуется, если это избегает неоднозначности для читателей вашего кода. В случае метода sorted(by:) цель замыкания ясна из того, что происходит сортировка, и читатель может безопасно предположить, что замыкание, скорее всего, работает со значениями String, потому что оно помогает при сортировке массива строк.

Неявное возвращение из замыканий с одной строкой кода

Замыкания с одной строкой кода могут неявно возвращать результат своей единственной строки кода, опуская ключевое слово return из своего объявления, как в этой версии предыдущего примера:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

Здесь тип функции аргумента метода sorted(by:) делает понятным, что замыкание должно возвращать значение типа Bool. Поскольку тело замыкания содержит единственное выражение (s1 > s2), которое возвращает значение типа Bool, нет никакой неоднозначности, и ключевое слово return можно опустить.

Сокращённые имена аргументов

Swift автоматически предоставляет сокращённые имена аргументов встроенным замыканиям, которые можно использовать для ссылки на значения аргументов замыкания по именам $0, $1, $2 и так далее.

Если вы используете эти сокращённые имена аргументов в своём выражении замыкания, вы можете опустить список аргументов замыкания из его определения. Тип сокращённых имён аргументов выводится из ожидаемого типа функции, и наибольшее по номеру сокращённое имя аргумента определяет количество аргументов, принимаемых замыканием. Ключевое слово in также можно опустить, потому что выражение замыкания состоит целиком из его тела:

reversedNames = names.sorted(by: { $0 > $1 } )

Здесь $0 и $1 ссылаются на первый и второй аргументы String замыкания. Поскольку $1 — это сокращённый аргумент с наибольшим номером, замыкание понимается как принимающее два аргумента. Поскольку функция sorted(by:) в данном случае ожидает замыкание, аргументы которого являются строками, сокращённые аргументы $0 и $1 являются обоих типа String.

Методы операторов

На самом деле есть ещё более короткое способ написания выражения замыкания выше. Тип Swift String определяет свою реализацию оператора больше, чем (>) для строк как метод, имеющий два параметра типа String и возвращающий значение типа Bool. Это точно соответствует типу метода, необходимому методу sorted(by:). Поэтому вы можете просто передать оператор больше, чем, и Swift выведет, что вы хотите использовать его реализацию для строк:

reversedNames = names.sorted(by: >)

Дополнительную информацию о методах операторов см. в <doc:AdvancedOperators#Operator-Methods>.

Замыкания в конце

Если вам нужно передать выражение замыкания функции в качестве последнего аргумента функции, и выражение замыкания длинное, может быть полезно написать его как замыкание в конце вместо этого. Вы пишете замыкание в конце после скобок вызова функции, даже если замыкание в конце всё ещё является аргументом функции. При использовании синтаксиса замыкания в конце вы не пишете метку аргумента для первого замыкания как часть вызова функции. Вызов функции может содержать несколько замыканий в конце; однако в первых нескольких примерах ниже используется одно замыкание в конце.

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
}

// Here's how you call this function without using a trailing closure:

someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})

// Here's how you call this function with a trailing closure instead:

someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

Выражение замыкания для сортировки строк из раздела <doc:Closures#Closure-Expression-Syntax> выше можно написать за пределами скобок метода sorted(by:) в качестве замыкания в конце:

reversedNames = names.sorted() { $0 > $1 }

Если в качестве единственного аргумента функции или метода используется выражение-замыкание, и вы предоставляете это выражение в качестве замыкания в конце, вам не нужно писать пару скобок () после имени функции или метода при вызове функции:

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

Замыкания в конце наиболее полезны, когда замыкание достаточно длинное, чтобы его нельзя было записать в одну строку. Например, тип Array языка Swift имеет метод map(_:), который принимает выражение-замыкание в качестве единственного аргумента. Замыкание вызывается один раз для каждого элемента в массиве и возвращает альтернативное сопоставленное значение (возможно, другого типа) для этого элемента. Вы определяете характер сопоставления и тип возвращаемого значения, написав код в замыкании, которое вы передаёте методу map(_:).

После применения предоставленного замыкания к каждому элементу массива метод map(_:) возвращает новый массив, содержащий все новые сопоставленные значения в том же порядке, что и соответствующие значения в исходном массиве.

Вот как вы можете использовать метод map(_:) с замыканием в конце, чтобы преобразовать массив значений Int в массив значений String. Массив [16, 58, 510] используется для создания нового массива ["OneSix", "FiveEight", "FiveOneZero"]:

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

Приведенный выше код создает словарь соответствий между целочисленными цифрами и английскими названиями этих цифр. Он также определяет массив целых чисел, готовый для преобразования в строки.

Теперь вы можете использовать массив numbers для создания массива значений String, передав выражение-замыкание в метод массива map(_:) в качестве замыкания в конце:

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

Метод map(_:) вызывает выражение-замыкание один раз для каждого элемента в массиве. Вам не нужно указывать тип входного параметра замыкания, number, так как тип может быть выведен из значений в массиве, которые необходимо сопоставить.

В этом примере переменная number инициализируется значением параметра number замыкания, чтобы это значение можно было изменить внутри тела замыкания. (Параметры функций и замыканий всегда являются константами.) Выражение-замыкание также указывает тип возвращаемого значения String, чтобы указать тип, который будет храниться в сопоставленном выходном массиве.

Выражение-замыкание строит строку с именем output каждый раз при вызове. Оно вычисляет последнюю цифру number, используя оператор остатка (number % 10), и использует эту цифру для поиска соответствующей строки в словаре digitNames. Замыкание может использоваться для создания строкового представления любого целого числа больше нуля.

Примечание: После обращения к индексу словаря digitNames следует знак восклицания (!), так как индексы словарей возвращают значение типа опционал, чтобы указать, что поиск по словарю может завершиться неудачей, если ключ не существует. В приведенном выше примере гарантируется, что number % 10 всегда будет допустимым ключом для словаря digitNames, поэтому используется знак восклицания для принудительного разворачивания значения String, хранящегося в опциональном значении, возвращаемом индексом.

Строка, извлеченная из словаря digitNames, добавляется в начало строки output, фактически создавая строковое представление числа в обратном порядке. (Выражение number % 10 дает значение 6 для 16, 8 для 58 и 0 для 510.)

Затем переменная number делится на 10. Поскольку это целое число, при делении происходит округление вниз, поэтому 16 становится 1, 58 становится 5, а 510 становится 51.

Процесс повторяется до тех пор, пока number не станет равным 0, в этот момент строка output возвращается замыканием и добавляется в выходной массив методом map(_:).

Использование синтаксиса замыкания в конце в приведенном выше примере хорошо обобщает функциональность замыкания сразу после функции, которую оно поддерживает, без необходимости заключать все замыкание в внешние скобки метода map(_:).

Если функция принимает несколько замыканий, вы опускаете метку аргумента для первого замыкания в конце и добавляете метки для оставшихся замыканий в конце. Например, функция ниже загружает изображение для фотогалереи:

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

При вызове этой функции для загрузки изображения вы предоставляете два замыкания. Первое замыкание — обработчик завершения, который отображает изображение после успешной загрузки. Второе замыкание — обработчик ошибок, который отображает ошибку пользователю.

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}

В этом примере функция loadPicture(from:completion:onFailure:) отправляет свою задачу по сети в фоновый процесс и вызывает один из двух обработчиков завершения, когда задача по сети завершается. Такая запись функции позволяет четко разделить код, отвечающий за обработку сетевых ошибок, от кода, обновляющего пользовательский интерфейс после успешной загрузки, вместо использования одного замыкания для обработки обоих случаев.

Примечание: Обработчики завершения могут стать трудночитаемыми, особенно когда необходимо вкладывать несколько обработчиков. Альтернативный подход заключается в использовании асинхронного кода, как описано в Concurrency.

Захват значений

Замыкание может захватывать константы и переменные из окружающего контекста, в котором оно определено. Замыкание может ссылаться на и изменять значения этих констант и переменных изнутри своего тела, даже если исходный область, которая определила константы и переменные, больше не существует.

В Swift простейшая форма замыкания, которая может захватывать значения, — это вложенная функция, написанная внутри тела другой функции. Вложенная функция может захватить любые аргументы своей внешней функции, а также любые константы и переменные, определенные внутри внешней функции.

Вот пример функции с названием makeIncrementer, которая содержит вложенную функцию с названием incrementer. Вложенная функция incrementer() захватывает два значения, runningTotal и amount, из своего окружающего контекста. После захвата этих значений функция incrementer возвращается функцией makeIncrementer в качестве замыкания, которое увеличивает runningTotal на amount каждый раз при вызове.

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

Тип возвращаемого значения makeIncrementer — это () -> Int. Это означает, что она возвращает функцию, а не просто значение. Возвращаемая функция не имеет параметров и возвращает значение типа Int каждый раз при вызове. Чтобы узнать, как функции могут возвращать другие функции, см. <doc:Functions#Function-Types-as-Return-Types>.

Функция makeIncrementer(forIncrement:) определяет целочисленную переменную runningTotal, чтобы хранить текущую сумму инкрементирования, которая будет возвращена. Эта переменная инициализируется значением 0.

Функция makeIncrementer(forIncrement:) имеет единственный параметр Int с меткой аргумента forIncrement и именем параметра amount. Значение аргумента, переданное этому параметру, определяет, на сколько runningTotal должно быть увеличено каждый раз при вызове возвращаемой функции инкрементирования. Функция makeIncrementer определяет вложенную функцию incrementer, которая выполняет фактическое увеличение. Эта функция просто добавляет amount к runningTotal и возвращает результат.

Если рассматривать вложенную функцию incrementer() изолированно, она может показаться необычной:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

Функция incrementer() не имеет параметров, но тем не менее ссылается на runningTotal и amount внутри своего тела. Она делает это, захватывая ссылку на runningTotal и amount из окружающей функции и используя их внутри собственного тела. Захват по ссылке гарантирует, что runningTotal и amount не исчезнут, когда вызов makeIncrementer закончится, а также гарантирует, что runningTotal доступна при следующем вызове функции incrementer.

Примечание: Для оптимизации Swift может вместо этого захватить и сохранить копию значения, если это значение не изменяется замыканием и если значение не изменяется после создания замыкания.

Swift также обрабатывает все операции по управлению памятью, связанные с удалением переменных, когда они больше не нужны.

Вот пример работы функции makeIncrementer:

let incrementByTen = makeIncrementer(forIncrement: 10)

В этом примере константа incrementByTen ссылается на функцию инкрементирования, которая добавляет 10 к своей переменной runningTotal каждый раз при вызове. Вызов функции несколько раз демонстрирует это поведение:

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

Если вы создадите вторую функцию инкрементирования, она будет иметь свою собственную сохраненную ссылку на новую, отдельную переменную runningTotal:

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

Вызов исходной функции инкрементирования (incrementByTen) снова увеличивает свою собственную переменную runningTotal и не влияет на переменную, захваченную функцией incrementBySeven:

incrementByTen()
// returns a value of 40

Примечание: Если вы присваиваете замыкание свойству экземпляра класса, а замыкание захватывает этот экземпляр, ссылаясь на него или его члены, вы создадите цикл сильной ссылки между замыканием и экземпляром. Swift использует списки захвата для разрыва таких циклов сильной ссылки. Более подробную информацию см. в <doc:AutomaticReferenceCounting#Strong-Reference-Cycles-for-Closures>.

Замыкания — типы ссылок

В приведенном выше примере incrementBySeven и incrementByTen — константы, но замыкания, на которые эти константы ссылаются, по-прежнему могут увеличивать переменные runningTotal, которые они захватили. Это потому, что функции и замыкания являются типами ссылок.

Всякий раз, когда вы присваиваете функцию или замыкание константе или переменной, вы фактически устанавливаете эту константу или переменную в качестве ссылки на функцию или замыкание. В приведенном выше примере константой является выбор замыкания, на которое incrementByTen ссылается, а не содержимое самого замыкания.

Это также означает, что если вы присваиваете замыкание двум различным константам или переменным, обе эти константы или переменные ссылаются на одно и то же замыкание.

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

incrementByTen()
// returns a value of 60

Приведенный выше пример демонстрирует, что вызов alsoIncrementByTen эквивалентен вызову incrementByTen. Поскольку оба они ссылаются на один и тот же замыкание, они оба инкрементируют и возвращают одну и ту же текущую сумму.

Выход за пределы области видимости замыканий

Замыкание считается выходящим за пределы области видимости функции, когда оно передается в функцию в качестве аргумента, но вызывается после возвращения функции. При объявлении функции, принимающей замыкание в качестве одного из параметров, можно написать @escaping перед типом параметра, чтобы указать, что замыкание может выходить за пределы области видимости функции.

Одним из способов выхода замыкания за пределы области видимости является его хранение в переменной, определенной вне функции. Например, многие функции, которые запускают асинхронную операцию, принимают аргумент-замыкание в качестве обработчика завершения. Функция возвращается после запуска операции, но замыкание не вызывается до завершения операции — замыкание должно выходить за пределы области видимости, чтобы быть вызванным позже. Например:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

Функция someFunctionWithEscapingClosure(_:) принимает замыкание в качестве аргумента и добавляет его в массив, объявленный вне функции. Если вы не отметили параметр этой функции с помощью @escaping, вы получите ошибку времени компиляции.

Выходящее за пределы области видимости замыкание, ссылающееся на self, требует особого рассмотрения, если self относится к экземпляру класса. Захват self в выходящем за пределы области видимости замыкании может привести к случайному созданию цикла сильной ссылки. Дополнительную информацию о циклах ссылок см. в разделе AutomaticReferenceCounting.

Обычно замыкание неявно захватывает переменные, используя их в теле замыкания, но в этом случае необходимо быть явным. Если вы хотите захватить self, напишите self явно при его использовании или включите self в список захвата замыкания. Явное написание self позволяет выразить намерение и напоминает о необходимости убедиться, что нет цикла ссылок. Например, в приведенном ниже коде замыкание, переданное в someFunctionWithEscapingClosure(_:), явно ссылается на self. В отличие от этого, замыкание, переданное в someFunctionWithNonescapingClosure(_:), является замыканием, не выходящим за пределы области видимости, что означает, что оно может ссылаться на self неявно.

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

Вот версия doSomething(), которая захватывает self, включив его в список захвата замыкания, а затем ссылается на self неявно:

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

Если self является экземпляром структуры или перечисления, вы всегда можете ссылаться на self неявно. Однако выходящее за пределы области видимости замыкание не может захватывать изменяемую ссылку на self, когда self является экземпляром структуры или перечисления. Структуры и перечисления не допускают совместной изменяемости, как описано в <doc:ClassesAndStructures#Structures-and-Enumerations-Are-Value-Types>.

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

Вызов функции someFunctionWithEscapingClosure в приведенном выше примере является ошибкой, потому что он находится внутри мутирующего метода, поэтому self является изменяемым. Это нарушает правило, согласно которому выходящие за пределы области видимости замыкания не могут захватывать изменяемую ссылку на self для структур.

Автозамыкания

Автозамыкание — это замыкание, автоматически созданное для обертывания выражения, передаваемого в функцию в качестве аргумента. Оно не принимает никаких аргументов, и при вызове возвращает значение выражения, которое в нем обернуто. Это синтаксическое удобство позволяет опустить фигурные скобки вокруг параметра функции, написав вместо этого обычное выражение вместо явного замыкания.

Обычным является вызов функций, принимающих автозамыкания, но не является обычным реализовывать этот вид функции. Например, функция assert(condition:message:file:line:) принимает автозамыкание для параметров condition и message; параметр condition вычисляется только в отладочных сборках, а параметр message вычисляется только если condition является false.

Автозамыкание позволяет отложить вычисление, так как код внутри не выполняется, пока вы не вызовете замыкание. Отложенное вычисление полезно для кода с побочными эффектами или с дорогостоящими вычислениями, так как позволяет контролировать время вычисления этого кода. Приведенный ниже код демонстрирует, как замыкание откладывает вычисление.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

Несмотря на то, что первый элемент массива customersInLine удаляется кодом внутри замыкания, элемент массива не удаляется до фактического вызова замыкания. Если замыкание никогда не вызывается, выражение внутри замыкания никогда не вычисляется, что означает, что элемент массива никогда не удаляется. Обратите внимание, что тип customerProvider не String, а () -> String — функция без параметров, возвращающая строку.

Вы получаете тот же эффект отложенного вычисления, когда передаете замыкание в функцию в качестве аргумента.

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

Функция serve(customer:) в приведенном выше списке принимает явное замыкание, возвращающее имя клиента. Версия serve(customer:) ниже выполняет ту же операцию, но вместо приема явного замыкания она принимает автозамыкание, отмечая тип параметра атрибутом @autoclosure. Теперь вы можете вызвать функцию так, как будто она принимает аргумент String вместо замыкания. Аргумент автоматически преобразуется в замыкание, так как тип параметра customerProvider отмечен атрибутом @autoclosure.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

Примечание: чрезмерное использование автозамыканий может сделать ваш код сложным для понимания. Контекст и имя функции должны четко указывать на то, что вычисление откладывается.

Если вам нужно автозамыкание, которое может выходить за пределы области видимости, используйте атрибуты @autoclosure и @escaping. Атрибут @escaping описан выше в <doc:Closures#Escaping-Closures>.

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

В приведенном выше коде вместо вызова замыкания, переданного в качестве аргумента customerProvider, функция collectCustomerProviders(_:) добавляет замыкание в массив customerProviders. Массив объявлен вне области видимости функции, что означает, что замыкания в массиве могут быть выполнены после возвращения функции. В результате значение аргумента customerProvider должно быть разрешено выходить за пределы области видимости функции.

This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API