Swift | Замыкания
Замыкания
Последнее обновление: 01.01.2018
Замыкания (сlosures) представляют самодостаточные блоки кода, которые могут использоваться многократно в различных частях программы, в том числе в виде параметров в функциях.
По сути функции являются частным случаем замыканий. Замыкания могут иметь одну из трех форм:
глобальные функции, которые имеют имя и которые не сохраняют значения внешних переменных и констант
вложенные функции, которые имеют имя и которые сохраняют значения внешних переменных и констант
замыкающие выражения (closure expressions), которые не имеют имени и которые могут сохранять значения внешних переменных и констант
В прошлых темах были рассмотрены глобальные и вложенные функции, поэтому в данной теме рассмотрим только замыкающие выражения.
Замыкающие выражения в общем случае имеют следующий синтаксис:
{ (параметры) -> тип_возвращаемого_значения in инструкции }
Если замыкания не имеют параметров или не возвращают никакого значения, то соответствующие элементы при определении замыкания могут опускаться.
Подобно тому, как переменная или константа могут представлять ссылку на функцию, они также могут представлять ссылку на замыкание:
let hello = { print("Hello world")} hello() hello()
В данном случае константе hello присваивается анонимная функция, которая состоит из блока кода, в котором выполняются некоторые действия. Эта функция не имеет никакого имени, мы ее можем вызывать только через константу hello.
Фактически константа hello в данном случае имеет тип ()->()
или ()-gt;Void
:
let hello: ()->Void = { print("Hello world")}
Дополнительно можно определить список параметров с помощью ключевого слова in:
let hello = { (message: String) in print(message) } hello("Hello") hello("Salut") hello("Ni hao")
В данном случае замыкание принимает один параметр — message, который представляет тип String. Список параметров
указывается до ключевого слова in, а после идут инструкции функции.
Также можно определить возвращаемое значение:
let sum = { (x: Int, y: Int) -> Int in return x + y } print(sum(2, 5)) // 7 print(sum(12, 15)) // 27 print(sum(5, 3)) // 8
Замыкания как аргументы функций
Как правило, анонимные функции используются в том месте, где они определены. Нередко анонимные функции передаются в другие функции в качестве параметра, если параметр представляет функцию:
func operation(_ a: Int, _ b: Int, _ action: (Int, Int) -> Int) -> Int{ return action(a, b) } let x = 10 let y = 12 let result1 = operation(x, y, {(a: Int, b: Int) -> Int in return a + b }) print(result1) // 22 var result2 = operation(x, y, {(a: Int, b: Int) -> Int in return a - b}) print(result2) // -2
Здесь функция operation()
в качестве третьего параметра принимает другую функцию, которой передаются
значения первого и второго параметров. Но нам необязательно определять дополнительные функции, поскольку в
operation мы можем передать замыкающее выражение.
В первом случае это выражение производит сложение параметров, а во втором случае — их вычитание.
Таким образом, замыкающие выражения позволяют нам сократить объем кода, поскольку не надо определять дополнительную функцию для передачи ее в качестве параметра. Но мы можем сократить код еще больше. Система может самостоятельно выводить тип параметров и тип возвращаемого значения, поэтому мы можем определение этих типов опускать:
let x = 10 let y = 12 let result1 = operation(x, y, {(a, b) in a + b }) print(result1) // 22 let result2 = operation(x, y, {(a, b) in a - b }) print(result2) // -2
Компилятор видит, замыкающее выражение передается в качестве значения для параметра типа
,
то есть в качестве функции, которая принимает параметры типа Int. Поэтому можно не указывать тип параметров a и b.
Также компилятор определяет, что функция возвращает значение типа Int, поэтому выражение после ключевого слова in
воспринимается как возвращаемое значение, и явным образом можно не использовать оператор return.
Но мы можем еще больше сократить замыкание, используя сокращения для параметров:
let x = 10 let y = 12 let result1 = operation(x, y, {$0 + $1}) print(result1) // 22 let result2 = operation(x, y, {$0 - $1}) print(result2) // -2
$0
представляет первый переданный в функцию параметр, а $1
— второй параметр. Система автоматически распознает их и поймет,
что они представляют числа.
Однако поскольку здесь выполняются примитивные операции — сложение и вычитание двух чисел, то мы можем сократить замыкания еще больше:
let x = 10 let y = 12 let result1 = operation(x, y, +) print(result1) // 22 let result2 = operation(x, y, -) print(result2) // -2
Система автоматически распознает,что должны выполняться операции сложения и вычитания двух переданных параметров, и поэтому результат
будет тот же.
Доступ к контексту
Замыкания имеют полный доступ к контексту, в котором они определены. Кроме того, замыкания могут использовать внешние переменные и константы как состояние, которое может храниться на всем протяжении жизни замыкания:
func action() -> (()->Int){ var val = 0 return { val = val+1 return val } } let inc = action() print(inc()) // 1 print(inc()) // 2
Здесь определена функция action, которая, в свою очередь, сама возвращает функцию. По факту она возвращает замыкающее выражение, которое увеличивает внешнюю переменную val на единицу и затем возвращает ее значение. Но при вызове мы видим, что переменная val сохраняет свое значение после увеличения, оно не сбрасывается обратно к нулю при каждом вызове функции. То есть переменная val представляет состояние, где замыкание может хранить данные.
Захват значений
Замыкающие выражения обладают способностью сохранять начальные значения переданных в них переменных. Например, рассмотрим следующую ситуацию:
var a = 14 var b = 2 let myClosure: () -> Int = {return a + b} print(myClosure()) // 16 a = 5 b = 6 print(myClosure()) // 11
Замыкающее выражение, на которое указывает константа myClosure, складывает значения переменных a и b. С изменением значений переменных также меняется результат замыкания myClosure. Однако мы можем зафиксировать начальные значения переменных:
var a = 14 var b = 2 let myClosure: () -> Int = {[a, b] in return a + b} print(myClosure()) // 16 a = 5 b = 6 print(myClosure()) // 16
Передав переменные в квадратные скобки: [a, b]
Используем замыкания в Swift по полной / Хабр
Несмотря на то, что в Objective-C 2.
Продвигаться будем от простого к сложному, от скучного к веселому. Заранее приношу извинения за обильное использование мантр «функция», «параметр» и «Double», но из песни слов не выкинешь.
1.1. Объекты первого класса
Для начала укрепимся с мыслью, что в Swift функции являются носителями гордого статуса объектов первого класса. Это значит, что функцию можно хранить в переменной, передавать как параметр, возвращать в качестве результата работы другой функции. Вводится понятие «типа функции».

Допустим, у нас есть две похожие функции, которые описывают две математические операции сложения и вычитания:
func add(op1: Double, op2: Double) -> Double { return op1 + op2 } func subtract(op1: Double, op2: Double) -> Double { return op1 - op2 }
Их тип будет описываться следующим образом:
(Double, Double) -> Double
Прочесть это можно так: «Перед нами тип функции с двумя входными параметрами типа Double и возвращаемым значением типа Double.»
Мы можем создать переменную такого типа:
// Описываем переменную
var operation: (Double, Double) -> Double
// Смело присваиваем этой переменной значение
// нужной нам функции, в зависимости от каких-либо условий:
for i in 0..<2 {
if i == 0 {
operation = add
} else {
operation = subtract
}
let result = operation(1.
0, 2.0) // "Вызываем" переменную
println(result)
}
Код, описанный выше, выведет в консоли:
3.0
-1.0
1.2. Замыкания
Используем еще одну привилегию объекта первого класса. Возвращаясь к предыдущему примеру, мы могли бы создать такую новую функцию, которая бы принимала одну из наших старых функций типа (Double, Double) -> Double в качестве последнего параметра. Вот так она будет выглядеть:
// (1)
func performOperation(op1: Double, op2: Double, operation: (Double, Double) -> Double) -> Double { // (2)
return operation(op1, op2) // (3)
}
Разберем запутанный синтаксис на составляющие. Функция performOperation принимает три параметра:
- op1 типа Double (op — сокращенное от «операнд»)
- op2 типа Double
- operation типа (Double, Double) -> Double
В своем теле performOperation просто возвращает результат выполнения функции, хранимой в параметре operation, передавая в него первых два своих параметра.

Пока что выглядит запутанно, и, возможно, даже не понятно. Немного терпения, господа.
Давайте теперь передадим в качестве третьего аргумента не переменную, а анонимную функцию, заключив ее в фигурные {} скобки. Переданный таким образом параметр и будет называться замыканием:
let result = performOperation(1.0, 2.0, {(op1: Double, op2: Double) -> Double in
return op1 + op2 // (5)
}) // (4)
println(result) // Выводит 3.0 в консоли
Отрывок кода (op1: Double, op2: Double) -> Double in — это, так сказать, «заголовок» замыкания. Состоит он из:
- псевдонимов op1, op2 типа Double для использования внутри замыкания
- возвращаемого значения замыкания -> Double
- ключевого слова in
Еще раз о том, что сейчас произошло, по пунктам:
(1) Объявлена функция performOperation
(2) Эта функция принимает три параметра.

(3) performOperation возвращает результат выполнения операции.
(4) В качестве последнего параметра в performOperation была передана функция, описанная замыканием.
(5) В теле замыкания указывается, какая операция будет выполняться над операндами.
Авторы Swift приложили немало усилий, чтобы пользователи языка могли писать как можно меньше кода и как можно больше тратить свое драгоценное время на
2.1. Избавляемся от типов при вызове.
Во-первых, можно не указывать типы входных параметров в замыкании явно, так как компилятор уже знает о них. Вызов функции теперь выглядит так:
performOperation(1.0, 2.0, {(op1, op2) -> Double in
return op1 + op2
})
2.

Во-вторых, если замыкание передается в качестве последнего параметра в функцию, то синтаксис позволяет сократить запись, и код замыкания просто прикрепляется к хвосту вызова:
performOperation(1.0, 2.0) {(op1, op2) -> Double in
return op1 + op2
}
2.3. Не используем ключевое слово «return».
Приятная (в некоторых случаях) особенность языка заключается в том, что если код замыкания умещается в одну строку, то результат выполнения этой строки автоматичеси будет возвращен. Таким образом ключевое слово «return» можно не писать:
performOperation(1.0, 2.0) {(op1, op2) -> Double in
op1 + op2
}
2.4. Используем стенографические имена для параметров.
Идем дальше. Интересно, что Swift позволяет использовать так называемые стенографические (англ. shorthand) имена для входных параметров в замыкании. Т.е. каждому параметру по умолчанию присваивается псевдоним в формате $n, где n — порядковый номер параметра, начиная с нуля.

performOperation(1.0, 2.0) { $0 + $1 }
Согласитесь, эта запись уже совсем не похожа на ту, которая была в самом начале.
2.5. Ход конем: операторные функции.
Все это были еще цветочки. Сейчас будет ягодка.
Давайте посмотрим на предыдущую запись и зададимся вопросом, что уже знает компилятор о замыкании? Он знает количество параметров (2) и их типы (Double и Double). Знает тип возвращаемого значения (Double). Так как в коде замыкания выполняется всего одна строка, он знает, что ему нужно возвращать в качестве результата его выполнения. Можно ли упростить эту запись как-то еще?
Оказывается, можно. Если замыкание работает только с двумя входными аргументами, в качестве замыкания разрешается передать операторную функцию, которая будет выполняться над этими аргументами (операндами).

Swift — в некотором роде забавный язык программирования. Надеюсь, статья будет полезной для тех, кто начинает знакомиться с этим языком, а также для тех, кому просто интересно, что там происходит у разработчиков под iOS и Mac OS X.
___________________________________________________________________
UPD.: Реальное применение
Некоторые пользователи высказали недовольство из-за отсутствия примеров из реальной жизни. Буквально вчера натолкнулся на задачу, которая может быть элегантно решена с использованием коротких замыканий.
Если вам нужно создать очередь с приоритетом, можно использовать двоичную кучу (binary heap). Как известно, это может быть как MinHeap, так и MaxHeap, т.е. кучи, где в корне дерева находится минимальный или максимальный элемент соотвественно. Базовая реализация MinHeap от MaxHeap будет отличаться по сути только проверочными сравнениями при восстановлении инварианта двоичной кучи после добавления/удаления элемента.
Таким образом, мы могли создать базовый класс BinaryHeap, который будет содержать свойство comparison типа (T, T) -> Bool. А конструктор этого класса будет принимать способ сравнения и затем использовать его в методах heapify. Прототип базового класса выглядел бы так:
class BinaryHeap<T: Comparable>: DebugPrintable {
private var array: Array<T?>
private var comparison: (T, T) -> Bool
private var used: Int = 0
// Бла-бла-бла
// Internal Methods
internal func removeTop() -> T? { //... }
internal func getTop() -> T? { //... }
// Public Methods:
func addValue(value: T) {
if used == self.array.count {
self.grow()
}
self.array[used] = value
heapifyToTop(used, comparison) // Одно из мест, где используется функция сравнения
self.used++
}
init(size newSize: Int, comparison newComparison: (T, T) -> Bool) {
array = [T?](count: newSize, repeatedValue: nil)
comparison = newComparison
}
}
Теперь для того, чтобы создать классы MinHeap и MaxHeap нам достаточно унаследоваться от BinaryHeap, а в их конструкторах просто явно указать, какое сравнение применять.

class MaxHeap<T: Comparable>: BinaryHeap<T> {
func getMax() -> T? {
return self.getTop()
}
func removeMax() -> T? {
return self.removeTop()
}
init(size newSize: Int) {
super.init(size: newSize, {$0 > $1})
}
}
class MinHeap<T: Comparable>: BinaryHeap<T> {
func getMin() -> T? {
return self.getTop()
}
func removeMin() -> T? {
return self.removeTop()
}
init(size newSize: Int) {
super.init(size: newSize, {$0 <= $1})
}
}
9.1. Замыкания — Основы | Swift World
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 выведено в тип [String]
// его значение равно ["OneSix", "FiveEight", "FiveOneZero"]
Теперь Вы можете использовать массив numbers для создания массива типа String
, передав в метод массива map(_:)
волочащееся замыкание.
Метод map(_:)
вызывает замыкание единожды для каждого элемента в массиве. Вам не нужно явно указывать тип входного параметра замыкания, number
, так как этот тип может быть выведен из значений отображаемого массива.
В этом примере переменная number
инициализируется значением параметра замыкания number
, так что это значение может быть изменено внутри тела замыкания. (Параметры функций и замыканий всегда константы.) Замыкание так же специфицирует возвращаемый тип String
, чтобы указать, какиой тип будет храниться в отображённом выходном массиве.
Замыкание строит строку с названием output
всякий раз при своём вызове. Оно вычисляет последнюю цифру number
с использованием оператора остатка от деления (number % 10)
и использует эту цифру для выбора подходящей строки в словаре digitNames
. Это замыкание может быть использовано для создания строки, отображающей любое целое число, большее 0.
Вызов сабскрипта словаря 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(_:)
.
Использование синтаксиса trailing-замыкания в примере выше позволяет инкаспулировать функциональность замыкания сразу после функции, поддерживающей это замыкание без необходимости оборачивать всё замыкание во внешнюю круглую скобку метода map(_:)
.
Замыкания в Swift: полное руководство по использованию
Замыкания – это блоки кода, которые могут быть многократно использованы. Понимание принципа работы замыканий является ключевым аспектом обучения в разработке под iOS.
Как работают замыкания?
Замыкания – это автономные блоки функциональности, которые можно передавать и использовать в вашем коде.
Иными словами, замыкание – это блок кода, который вы можете присвоить переменной. Затем вы можете передать его в своем коде, например, в другую функцию.
Давайте посмотрим на аналогию:
- Боб говорит Алисе: «Помаши руками!» Алиса слышит инструкцию и машет руками. Размахивание руками – это функция, которую Боб вызвал напрямую.
- Алиса записывает свой возраст на листе бумаги и передает его Бобу. Лист бумаги является переменной. Алиса использовала лист бумаги для сохранения данных.
- Боб пишет: «Помашите руками!» на листе бумаги и дает его Алисе. Алиса читает инструкцию на листе бумаги и машет руками. Инструкция, переданная на листе бумаги, является замыканием.
Позже мы можем отправить этот лист бумаги по почте или сказать Алисе, чтобы она прочитала его после того, как закончит выполнять другое задание. Это одно из преимуществ использования замыканий.
Напишем замыкание:
let birthday = { print("С днем рождения!") } birthday() // С днем рождения!
- В первой строке мы определяем замыкание и назначаем его константе birthday. Замыкание – это код между фигурными скобками. Обратите внимание, что замыкание назначается birthday с помощью оператора присваивания =.
- Когда вы запускаете код, замыкание вызывается с помощью синтаксиса birthday(). То есть мы используем имя константы birthday с круглыми скобками (). Это похоже на вызов функции.
Как и функции, замыкания могут иметь параметры.
let birthday: (String) -> () = { name in print("С днем рождения, \(name)!") } birthday("Александр") // С днем рождения, Александр!
- Как и раньше, мы объявляем замыкание в первой строке, затем назначаем его константе birthday и вызываем замыкание в последней строке.
- Замыкание теперь имеет один параметр типа String. Этот параметр объявлен как тип замыкания – (String) -> ()
- Затем вы можете использовать параметр name в замыкании. При вызове замыкания мы указываем значение параметра.
Здесь важны три вещи:
- Тип замыкания – (String) -> ().
- Код замыкания – { name in ··· }.
- Вызов замыкания – birthday(···).
Параметры замыкания не имеют названий в отличие от функций. Когда вы объявляете замыкание, вы можете указать типы параметров, которые у него есть, например, String. В коде замыкания вы назначаете локальную переменную первому параметру. Это дает параметру имя в замыкании.
Мы можем опустить имя переменной и использовать сокращение для первого параметра – $0:
let birthday: (String) -> () = { print("С днем рождения, \($0)!") }
В приведенном выше коде замыкание birthday имеет один параметр. Внутри замыкания сокращение используется для ссылки на значение первого параметра – $0.
Типы замыканий
Каждое замыкание имеет тип, как и любая другая переменная или константа.
Мы объявляем замыкание с одним параметром следующим образом:
let birthday: (String) -> () = { (name: String) -> () in ··· }
Замыкание имеет один параметр типа и возвращает (). Первый параметр замыкания также принимает данный тип (name: String) -> (). Ключевое слово in отделяет параметры замыкания от кода.
Синтаксис для типа замыкания состоит из типов параметров и типа возвращаемого значения:
- (Int, Int) -> Double – имеет 2 параметра Double и возвращает значение Double.
- () -> Int – не имеет параметров и возвращает целое число.
- (String) -> String – принимает строку и возвращает строку.
Давайте рассмотрим код самого замыкания:
{ (имя параметра: тип параметра) -> тип возвращаемого значения in ··· }
Тип замыкания объявляется несколько иначе и включает имена параметров. Посмотрим на несколько примеров:
- (Int, Int) -> Double становится { (width: Int, height: Int) -> Double in ···}
- () -> Int становится { () -> Int in ···}
- (String) -> String становится { (text: String) -> String in ···}
Вы можете называть эти параметры как угодно, но вам нужно будет дать им имена. Эти константы могут использоваться «локально» внутри замыкания по аналогии с параметрами внутри тела функции.
in проще всего воспринимать как “У нас есть параметры X, Y, Z в блоке кода, который является замыканием”.
Замыкание без параметров и без возвращаемого значения имеет следующий тип:
Вы также можете использовать Void в качестве типа возвращаемого значения. В Swift Void означает «ничего»:
Посмотрим на последний пример:
let greeting: (String, String) -> String = { (time: String, name: String) -> String in return "Отличное \(time), \(name)!" } let text = greeting("утро", "Александр") print(text) // Отличное утро, Александр!
Замыкание greeting имеет два параметра типа String. Также замыкание возвращает значение типа String. Тип greeting явно определен, как и параметры замыкания.
Когда вызывается замыкание, ему предоставляется два аргумента типа String, и его возвращаемое значение присваивается text, а затем выводится на консоль.
Замыкания и вывод типа
Swift может сам выводить типы. Когда вы не указываете явно тип переменной, Swift может самостоятельно определить, какой тип у данной переменной. Это зависит от контекста вашего кода.
Swift выводит тип age на основании контекста. 104 – это значение для целого числа, так что константа age имеет тип Int. Swift выясняет это самостоятельно без необходимости явно указывать тип.
Вывод типа часто используется в замыканиях. В результате вы можете опустить часть кода для замыкания.
let names = ["Zaphod", "Slartibartfast", "Trillian", "Ford", "Arthur", "Marvin"] let sortedNames = names.sorted(by: <) print(sortedNames) // ["Arthur", "Ford", "Marvin", "Slartibartfast", "Trillian", "Zaphod"]
Мы создаем массив с именами, а затем сортируем их в алфавитном порядке, вызывая функцию sorted(by:). Параметр by: принимает замыкание <, которое используется для сортировки массива.
Рассмотрим полный код замыкания:
names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 < s2 })
Здесь используется полный синтаксис замыкания, включая два имени параметра s1 и s2 типа String, а также тип возвращаемого значения Bool. Мы также используем ключевые слова in и return .
Этот код можно сократить:
names.sorted(by: { s1, s2 in return s1 < s2 } )
Здесь пропускаются типы параметров замыкания, поскольку они могут быть выведены из контекста. Поскольку мы сортируем массив строк, эти два параметра выводятся как String. Опуская типы, мы также можем опустить окружающие их скобки. Также мы опускаем тип возвращаемого значения.
names.sorted(by: { s1, s2 in s1 < s2 } )
Здесь опущен return, потому что замыкание представляет собой только одну строку кода.
names.sorted(by: { $0 < $1 } )
Мы используем сокращенные имена для первого и второго параметра замыкания.
Когда замыкание является последним или единственным параметром функции, вы можете написать код замыкания вне скобок функции. Это называется выходящее замыкание (traling closure).
Здесь мы используем оператор < в качестве замыкания. В Swift операторы являются функциями верхнего уровня. Его тип (lhs: (), rhs: ()) -> Bool, что соответствует типу sorted(by:).
Замыкания и захват значений
В Swift замыкания захватывают переменные и константы из окружающей их области видимости.
Каждая переменная, функция и замыкание имеет свою область видимости. Область видимости определяет, где вы можете получить доступ к определенной переменной, функции или замыканию. Если переменная, функция или замыкание не находятся в области видимости, вы не можете получить к ним доступ. Область видимости иногда называется «контекстом».
Любой код имеет глобальные и локальные области видимости. К примеру:
- Свойство, определенное в классе, является частью глобальной области видимости. В любом месте этого класса вы можете установить и получить значение свойства.
- Переменная, как определено в функции, является частью локальной области действия функции. В любом месте этой функции вы можете установить и получить значение переменной.
Рассмотрим пример того, как замыкание захватывает окружающую область видимости:
let name = "Александр" let greeting = { print("Не паникуй, \(name)!") } greeting() // Не паникуй, Александр!
Константа name присваивается значение “Александр” типа String. Затем создается замыкание и назначается константе greeting. Замыкание выводит некоторый текст. Наконец, замыкание выполняется путем вызова greeting().
В этом примере замыкание захватывает значение переменной name. То есть инкапсулирует переменные, которые доступны в области, в которой определено замыкание. В результате мы можем получить доступ к name даже если оно не объявлено локально в замыкании.
Посмотрим на более сложный пример:
func addScore(_ points: Int) -> Int { let score = 42 let calculate = { return score + points } return calculate() } let value = addScore(11) print(value) // 53
Сначала мы создаем функцию addScore(_:), которое возвращает новое значение на основании параметра и предустановленного значения внутри функции. Затем внутри функции определяется замыкание calculate, которое добавляет score и points, а затем возвращает результат. Функция возвращает замыкание calculate().
При этом замыкание calculate захватывает оба значения score и points. Ни одна из этих переменных не объявляется локально в замыкании, но замыкание может получить доступ к их значениям.
Сильные ссылки и захват значений
Когда замыкание захватывает значение, оно автоматически создает сильную ссылку на это значение.
Когда Боб имеет сильную ссылку на Алису, Алиса не удаляется из памяти, пока Боб не будет удален из памяти. Но что, если у Алисы есть сильная ссылка на Боба? Тогда и Боб, и Алиса не будут удалены из памяти, потому что они держатся друг за друга. Боб не может быть удален, потому что Алиса держит его, а Алиса не может быть удалена, потому что Боб держит ее.
Это называется сильным циклом ссылки (strong reference cycle) и вызывает утечку памяти. Представьте, что сотня Бобов и Алис занимают по 10 МБ в памяти и тогда у нас определенно возникнет проблема.
Память в iOS управляется с помощью концепции под названием автоматический подсчет ссылок (Automatic Reference Counting или ARC). Большая часть управления памятью с помощью ARC сделана за вас, но вы должны избегать сильных циклов ссылок.
Вы можете разорвать цикл сильных ссылок, связанных с захватом значений. Так же, как вы можете пометить свойство как weak, вы можете пометить захваченные значения в замыкании как weak или unowned ссылку.
class Database { var data = 0 } let database = Database() database.data = 11010101 let calculate = { [weak database] multiplier in return database!.data * multiplier } let result = calculate(2) print(result)
Сначала мы определяем класс Database. У него есть одно свойство data. Затем мы создаем экземпляр Database с именем database и устанавливаем для его свойства data целочисленное значение. Далее мы создаем замыкание calculate. Замыкание принимает один аргумент multiplier. Внутри замыкания data умножается на multiplier. Наконец, замыкание вызывается с аргументом 2 и его результат присваивается result.
Ключевой частью кода здесь является список захвата:
··· { [weak database] ···
Список захвата – это список имен переменных, разделенных запятыми, с префиксом weak или unowned, заключенный в квадратные скобки.
[weak self] [unowned navigationController] [unowned self, weak database]
Мы используете список захвата, чтобы указать, что на конкретное захваченное значение нужно ссылаться как weak или unowned. weak или unowned нарушает цикл сильных ссылок, поэтому замыкание не будет удерживать захваченный объект.
- Ключевое слово weak указывает на то, что захваченное значение может стать nil.
- Ключевое слово unowned указывает на то, что захваченное значение не становится nil.
Мы обычно используем unowned, когда замыкание и захваченное значение будут ссылаться друг на друга и будут освобождены одновременно. Примером является [unowned self] в View Controller. Замыкание уничтожается вместе с контекстом.
Мы обычно используем weak, когда зафиксированное значение в какой-то момент становится nil. Это может произойти, когда замыкание переживает контекст, в котором оно было создано. Например, View Controller, который освобождается до завершения длительной задачи. В результате захваченное значение является опциональным.
Обработчки завершения (completion handlers)
Распространенным применением замыканий является обработчик завершения.
- Вы выполняете длительную по времени задачу в своем коде, например, загружаете файл, делаете расчет или ждете ответа веб-сервиса.
- Вы хотите выполнить некоторый код, когда долгая задача будет завершена, но вы не хотите постоянно выяснять статус, чтобы узнать, завершена ли она.
- Вместо этого вы предоставляете замыкание для долгой задачи, которое будет вызвано, когда задача будет завершена (отсюда название «обработчик завершения»).
let task = session.dataTask(with: "http://example.com/api", completionHandler: { data, response, error in // })
В приведенном выше коде мы делаем сетевой запрос на загрузку некоторых данных и что-то делаем с этими данными, когда запрос будет завершен.
Вы можете писать код так, как будто между началом запроса и его завершением нет времени. Вместо того, чтобы ждать завершения длинной задачи, мы можем просто предоставить код, который будет выполнен по завершению задачи, в то время как мы кодируем сетевой запрос здесь и сейчас.
Запускается длинная задача, и мы определяем обработчик завершения. Обработчик завершения является замыканием, поэтому он захватывает переменные в окружающей его области. Длительная задача завершается, и ваш обработчик выполнения выполняется. Область действия замыкания сохраняется, поэтому мы можем использовать любые переменные и константы, определенные в области замыкания.
К примеру, мы хотим использовать данные сетевого запроса для отображения изображения:
let imageView = UIImageView() HTTP.request("http://imgur.com/kittens", completionHandler: { data in imageView.image = data })
Мы определям изображение с помощью UIImageView(), запускаете сетевой запрос и предоставляем обработчик завершения. Обработчик завершения выполняется, когда длинная задача будет завершена.
Замыкание захватило ссылку imageView, так что вы можете установить данные изображения, когда длинная задача будет завершена.
Swift-передача замыкания в метод — CodeRoad
Я только начал изучать Swift после 2 лет отсутствия разработки iOS и, похоже, застрял в закрытии как синтаксис аргумента. У меня есть следующий метод в классе:
func onFollwersButtonTouched(cb: () -> Void) {
self.
onFollowingTouchedCb = cb
}
И я пытаюсь установить этот обратный вызов на другой класс:
cell.onFollowersTouchedCb({ () -> Void in
})
Этот код не компилируется. Ошибка компилятора заключается в следующем:
Error:(115, 14) cannot convert the expression's type '() -> Void' to type '() -> Void'
И я понятия не имею, что происходит. Я попробовал этот синтаксис в книге Apple Swift, но и он оказался неудачным.
ios swiftПоделиться Источник Raphael 26 марта 2015 в 12:08
2 ответа
1
Вы должны либо вызвать метод с параметром закрытия:
cell.onFollowersTouched({ () -> Void in
})
или назначить закрытие переменной:
cell.onFollowersTouchedCb = { () -> Void in
}
В настоящее время вы вызываете onFollowersTouchedCb
с параметром закрытия, в то время как параметры не объявлены.
Поделиться Kirsteins 26 марта 2015 в 12:19
1
Этот:
cell.onFollowersTouchedCb({ () -> Void in
})
это не установка закрытия. Вам нужно выполнить фактическое назначение:
cell.onFollowersTouchedCb = {
// Do something
}
Поделиться Firo 26 марта 2015 в 12:23
Похожие вопросы:
Передача замыкания рекурсивной функции
Я работаю над четверным деревом. Использование замыкания для определения того, когда регион должен быть разделен, кажется хорошей идеей. pub fn split_recursively(&mut self, f: |Rect| -> bool)…
Вызов функции и передача параметров из замыкания в Swift
Я делаю запрос HTTP с NSURLSession , который требует закрытия, которое будет выполняться в конце запроса. Из замыкания я пытаюсь вызвать функцию и передать ей объект returnList . Проблема: когда я…
Передача функции с несколькими аргументами в качестве замыкания в Swift
Можно ли рассматривать функцию/метод с несколькими аргументами как замыкание в Swift? Я особенно спрашиваю о случае, когда начальное имя функции совпадает с другими функциями и дифференцируется по…
Можно ли перегрузить статический метод сигнатурой закрытия в Swift 3?
Можно ли перегрузить статический метод по типу замыкания в Swift 3? Например, у меня есть структура с 2 методами: struct Some { static func doSomething(first: String, @escaping completion: ([Int]?)…
Swift 3.0 выражение замыкания: что делать, если вариационные параметры не находятся на последнем месте в списке параметров?
Обновление на 2016.09.19 Существует хитрый, косвенный способ использовать вариационные параметры перед некоторыми другими параметрами в списке параметров выражения закрытия, ха-ха let testClosure =. ..
Передача данных между контроллерами в Swift
Я хочу, чтобы передача данных между TableViewController и ViewController программой не входила в метод Мой код swift: override func unwind(for unwindSegue: UIStoryboardSegue, towardsViewController…
Эквивалент закрытия Swift trailing в C++
В Swift, когда нам нужно передать замыкание в качестве параметра функции, если замыкание является последним передаваемым параметром, мы можем указать тело замыкания после последней скобки )…
Является ли метод skip() операцией короткого замыкания?
Я читаю об операциях короткого замыкания потоков Java и нашел в некоторых статьях, что skip() — это операция короткого замыкания. В другой статье они не упоминали skip() как операцию короткого…
как реализовать обратный вызов из функции C++ в функцию Swift
Я попробовал сделать что-то в классе cpp и при выполнении функции вернуть обратный вызов в swift. Так что я делаю эти вещи : Создание этой функции в callbackClass.cpp int callback::run(void…
Swift 4 из Swift 3 преобразование — Void ожидает 2 аргумента
Я получаю ошибку в Swift 4, но не слишком уверен, что ожидает Xcode. Настоящий кодекс: _connection.on(method: locationUpdate, callback: {(locationUpdate: LocationUpdate) in…
Дождитесь завершения замыкания в середине цепочки функций в SWIFT
Мне было интересно, могу ли я дождаться завершения замыкания в цепочке функций, прежде чем код перейдет к следующей функции.
Например:
SomeSingletonClass.sharedInstance.bFunction()
И если это первый вызов sharedInstance, я бы хотел, чтобы bFunction() подождал, пока closure* в init() из SingletonClass не закончится. Возможно ли это как-то без блокировки UI?
*This закрытие может показать предупреждение о предоставлении разрешения доступа, и bFunction() должен ждать реакции пользователя на это предупреждение (или даже не может вызвать bFunction вообще, в зависимости от ответа пользователя).
Спасибо за любой совет в продвинутом режиме.
Поэтому, как и предполагал GoZoner, я попытался заблокировать выполнение в методе init()
с помощью dispatch_semaphore_create() / signal() / wait()
, но в этом случае приложение блокируется и не может показать AlertView.
Поделиться Источник danieltmbr 05 февраля 2015 в 04:04
3 ответа
- Передача дополнительного параметра в середине цепочки обещаний
Мне нужно бросить userID param в середине цепочки обещаний(это единственное обещание, которое нуждается в нем). Все обещания должны выполняться в синхронном порядке. SideNote — все подобные примеры в stackoverflow немного отличаются — например, использование функций lambda(я использую объявленные…
- IOS, Swift, анимация цепочки, ожидание основного потока до завершения цепочки
Этот вопрос был задан по крайней мере столько раз, сколько я искал ответ, но никогда к моему удовлетворению.
У меня есть вид с 52 подвидами, представляющими колоду карт. Карты появляются в центре экрана, разделяются на две стопки карт, а затем сливаются вместе, как перетасованная колода. Если я…
1
Вы можете опубликовать NSNotification
из закрытия NSNotificationCenter.defaultCenter().postNotificationName("weirdKeyName", object: nil)
И слушайте это уведомление и вызывайте свое оповещение или что-то еще, что вам нужно сделать, когда код закрытия будет выполнен, как только придет уведомление: NSNotificationCenter.defaultCenter().addObserver(self, selector: "alertFunction", name: "weirdKeyName, object: nil)
alertFunction() {
...
}
Ожидание NSNotification для запуска вашего предупреждения не приведет к блокировке вашего приложения и ожиданию завершения закрытия.
Кроме того, если вы этого хотите, вы можете отправить уведомление в фоновый поток с помощью NSNotificationQueue
Поделиться Leonardo 14 февраля 2015 в 17:09
1
Метод init()
для вашего SomeSingletonClass
должен будет блокировать его выполнение до тех пор, пока не завершится оповещение о предоставлении разрешения доступа. По завершении работы метод
init()
возобновляется, возвращается свойство sharedInstance
и метод bFunction()
применяется к возвращаемому экземпляру.
Существует несколько способов заблокировать выполнение; то, что вы используете, будет зависеть от деталей многопроцессорной обработки вашего приложения.
Поделиться GoZoner 05 февраля 2015 в 05:10
0
Вы рассматривали этот ваш код
SomeSingletonClass.sharedInstance.bFunction()
Может быть, он сможет жить в потоке, отличном от основного потока?
Таким образом, функция sharedInstance (которая инициализирует SomeSingletonClass
) может запросить основной поток асинхронно создать инициализированное значение, а семафор дождаться его завершения?
Таким образом, вы не блокируете основной поток.
Поделиться Alex Brown 12 февраля 2015 в 17:20
Похожие вопросы:
Дождитесь завершения запроса Active Directory перед выполнением следующей строки
Пытаюсь понять, как я могу сделать приведенный ниже код: Дождитесь завершения строки 1, Прежде чем продолжить. Ждать 4 линия, чтобы завершить перед запуском линии 5 . $invokevar = Get-ADComputer…
Дождитесь завершения асинхронной операции в Swift
Я не знаю, как справиться с этой ситуацией, так как я очень новичок в разработке iOS и Swift. Я выполняю выборку данных вот так: func application(application: UIApplication!,…
Дождитесь завершения цепочки вызовов AJAX
У меня есть функция, которая вызывает другие функции, которые имеют AJAX запрос. Я хочу, чтобы все вызовы AJAX завершились до вызова последней функции. Я инициализирую массив, и мне нужно, чтобы все…
Передача дополнительного параметра в середине цепочки обещаний
Мне нужно бросить userID param в середине цепочки обещаний(это единственное обещание, которое нуждается в нем). Все обещания должны выполняться в синхронном порядке. SideNote — все подобные примеры…
IOS, Swift, анимация цепочки, ожидание основного потока до завершения цепочки
Этот вопрос был задан по крайней мере столько раз, сколько я искал ответ, но никогда к моему удовлетворению. У меня есть вид с 52 подвидами, представляющими колоду карт. Карты появляются в центре…
Дождитесь завершения синтаксического анализа асинхронных функций в Swift
Я пытаюсь дождаться разбора асинхронных функций в Swift , чтобы перезагрузить мой UITableView Я не уверен, что обработчик завершения полезен в этом случае. или отправить асинхронно. Я действительно…
Дождитесь завершения функций в пределах обещания
У меня есть следующая функция в контроллере Angular.js: vm.some_action = function() { var promise = IDService.getUserInfo().then(function(user) { vm.user.name = user.name; vm.box.color = ‘red’;…
Переход к промежуточным значениям в середине цепочки функционального программирования
Мне интересно, есть ли краткий или конкретный способ доступа к значениям в середине цепочки FP в JavaScript. Пример: const somestuff = [true, true, false]; let filteredCount = 0;…
Эквивалент закрытия Swift trailing в C++
В Swift, когда нам нужно передать замыкание в качестве параметра функции, если замыкание является последним передаваемым параметром, мы можем указать тело замыкания после последней скобки ). ..
Добавление комментария в середине цепочки вызовов
Как я могу добавить комментарий в середине цепочки вызовов? Я вижу, что Ruby выдает ошибку синтаксического анализа, когда я пытаюсь добавить комментарий в середине цепочки вызовов, например:…
Swift: Closures (Замыкания) | Каморка сурового программиста
Замыкания подобны блокам в Objective-C
Так называются блоки функциональности, захватывающие переменные из контекста
Синтаксис:
{ (parameters) -> returnType in
…//код
}
По большому счету это обрамленный в фигурные скобки тип функции + оператор in + тело функции
Бывает три разновидности замыканий
– глобальные функции – замыкание имеющее имя, и не захватывающее никаких значений
– вложенные функции, – замыкание имеющее имя и захватывающее значение из ограждающей ее функции
– легковесные замыкания без имени, захватывающие значения из окружающего их контекста
Запрещены к применению значения по умолчанию, variadic параметр разрешен только если он именован и помещен в конце списка параметров
Рассмотрим библиотечную функцию sorted (для swift 1. 2) имеющую определение следующего вида
func sorted<C: SequenceType>(source: C, isOrderedBefore: (C.Generator.Element, C.Generator.Element) -> Bool) -> [C.Generator.Element]
func sorted<C: SequenceType>(source: C, isOrderedBefore: (C.Generator.Element, C.Generator.Element) -> Bool) -> [C.Generator.Element] |
Для простоты перепишем определение проще в виде частного случая если бы sorted принимала только массив целых чисел
func sorted (source: [Int], isOrderedBefore:(Int, Int) -> Bool) -> [Int]
func sorted (source: [Int], isOrderedBefore:(Int, Int) -> Bool) -> [Int] |
вторым параметром выступает тип функции isOrderedBefore:(Int, Int) -> Bool
Мы можем написать так
func ascending(first: Int, second: Int) -> Bool { return first < second }
func ascending(first: Int, second: Int) -> Bool { return first < second } |
и вызвать sorted так
var sortedArray = sorted([1, 5, 2], ascending) // [1,2,5]
var sortedArray = sorted([1, 5, 2], ascending) // [1,2,5] |
В Swift 2.

var sortedArray = [1, 5, 2].sort(ascending) // [1,2,5]
var sortedArray = [1, 5, 2].sort(ascending) // [1,2,5] |
Но если вызов единственный и городить отдельную функцию для этого нецелесообразно? Тут и приходят на помощь замыкания
Swift 1.2
var sortedArray = sorted([1, 5, 2], { (first: Int, second: Int) -> Bool in return first < second })
var sortedArray = sorted([1, 5, 2], { (first: Int, second: Int) -> Bool in return first < second }) |
Swift 2.0
var sortedArray = [1, 5, 2].sort({ (first: Int, second: Int) -> Bool in return first < second })
var sortedArray = [1, 5, 2]. return first < second }) |
Но мы можем себе упростить жизнь, т.к. swift знает из определения функции sorted и переданного массива какого типа будут параметры и тип возвращаемого значения у типа функции – их можно опустить
Swift 1.2
var sortedArray = sorted([1, 5, 2], { first, second in return first < second })
var sortedArray = sorted([1, 5, 2], { first, second in return first < second }) |
Swift 2.0
var sortedArray = [1, 5, 2].sort({ first, second in return first < second })
var sortedArray = [1, 5, 2].sort({ first, second in return first < second }) |
В однострочном варианте можно опустить слово return
Swift 1. 2
var sortedArray = sorted([1, 5, 2], { first, second in first < second })
var sortedArray = sorted([1, 5, 2], { first, second in first < second }) |
Swift 2.0
var sortedArray = [1, 5, 2].sort({ first, second in first < second })
var sortedArray = [1, 5, 2].sort({ first, second in first < second }) |
Можно так же опустить именование переменных и получить к ним доступ по индексу, опустив помимо всего прочего оператор in
Swift 1.2
var sortedArray = sorted([1, 5, 2], { $0 < $1 })
var sortedArray = sorted([1, 5, 2], { $0 < $1 }) |
Swift 2.

var sortedArray = [1, 5, 2].sort({ $0 < $1 })
var sortedArray = [1, 5, 2].sort({ $0 < $1 }) |
Т.к. для Int определен оператор
var sortedArray = sorted([1, 5, 2], <)
var sortedArray = sorted([1, 5, 2], <) |
var sortedArray = [1, 5, 2].sort(<)
var sortedArray = [1, 5, 2].sort(<) |
func inPlaceClosure(closure: () -> () { // тело })
func inPlaceClosure(closure: () -> () { // тело }) |
Стало
func inPlaceClosure(closure: () -> ()) { // тело }
func inPlaceClosure(closure: () -> ()) { // тело } |
Ну или переписав наш пример
Swift 1. 2
var sortedArray = sorted([1, 5, 2]) { $0 < $1 }
var sortedArray = sorted([1, 5, 2]) { $0 < $1 } |
var sortedArray = [1, 5, 2].sort(){ $0 < $1 }
var sortedArray = [1, 5, 2].sort(){ $0 < $1 } |
func addBy(number: Int) -> () -> Int { var sum = 0 func incrementer() -> Int { sum += number return sum } return incrementer } let incrementBy5 = addBy(5) print(incrementBy5()) // 5 print(incrementBy5()) //10
func addBy(number: Int) -> () -> Int { var sum = 0 func incrementer() -> Int { sum += number return sum } return incrementer }
let incrementBy5 = addBy(5) print(incrementBy5()) // 5 print(incrementBy5()) //10 |
Таким образом при создании функции incrementer внутри функции addBy были захвачены переменная sum и константа number
let incrementBy3 = addBy(3) print(incrementBy3()) // 3 print(incrementBy5()) // 15
let incrementBy3 = addBy(3) print(incrementBy3()) // 3 print(incrementBy5()) // 15 |
Создав новый incrementBy3 мы убедились что переменная sum в нем не зависит от sum в предыдущем incrementBy5. Т.е. при каждом создании замыкания – в него захватывается текущее окружение.
Переменные захватываются по ссылке, а не по значению
func addByWithChange(number: Int) -> () -> Int { var sum = 0 func incrementer() -> Int { sum += number return sum } sum = 100 return incrementer } let incrementBy1 = addByWithChange(1) print(incrementBy1()) // 101 а не 1
func addByWithChange(number: Int) -> () -> Int { var sum = 0 func incrementer() -> Int { sum += number return sum } sum = 100 return incrementer }
let incrementBy1 = addByWithChange(1) print(incrementBy1()) // 101 а не 1 |
Особенно важно помнить о захвате переменных при работе с Cocoa, где захват self может произойти совершенно незаметно и создаст сильную связь (strong reference), в итоге текущий объект и замыкание создадут классическую циклическую связь создав утечку памяти.
Ну и не забываем что замыкание – ссылочного типа.
Swift Closures и их типы (с примерами)
В статье Функции Swift мы создали функцию, используя ключевое слово func
. Однако в Swift есть еще один особый тип функций, известный как closures , который можно определить без использования ключевого слова func
и имени функции.
Подобно функциям, замыкания могут принимать параметры и возвращать значения. Он также содержит набор операторов, которые выполняются после того, как вы его вызываете, и могут быть назначены переменной / константе как функции.
Пробкив основном используются по двум причинам:
- Блоки завершения
Замыкания помогают вам получать уведомления, когда некоторая задача завершила свое выполнение. См. Closure как обработчик завершения, чтобы узнать о нем больше. - Функции высшего порядка
Замыкания могут передаваться в качестве входных параметров для функций высшего порядка.Функция более высокого порядка — это просто тип функции, которая принимает функцию в качестве входа и возвращает значение типа функция в качестве выхода.
Для этой цели лучше использовать замыкания вместо функции, потому что закрытие опускает ключевое слово func и имя функции, что делает код более читаемым и коротким.
Синтаксис закрытия
Синтаксис выражения закрытия имеет следующий общий вид:
{(параметры) -> тип возврата в заявления }
Обратите внимание на использование в ключевом слове
после типа возврата. Ключевое слово in
используется для разделения типа возврата и операторов внутри замыкания.Замыкание принимает параметры и может возвращать значение. Давайте научимся создавать свои собственные закрытия на примерах ниже:
Пример 1: Простое закрытие
let simpleClosure = {
}
simpleClosure ()
В приведенном выше синтаксисе мы объявили простое закрытие {}
, которое не принимает параметров, не содержит операторов и не возвращает значения. Это закрытие присвоено константе simpleClosure .
Мы называем закрытие simpleClosure ()
, но, поскольку оно не содержит никаких операторов, программа ничего не делает.
Пример 2: Замыкание, содержащее операторы
let simpleClosure = {
print ("Привет, мир!")
}
simpleClosure ()
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, мир!
В приведенной выше программе вы определили замыкание simpleClosure . Тип simpleClosure предполагается равным () -> ()
, потому что он не принимает параметр и не возвращает значение.
Если вы хотите явно определить тип закрытия, вы можете сделать это как let simpleClosure :() -> ()
.
В отличие от первого примера, вызов закрытия как simpleClosure ()
выполняет внутри него оператор print ()
. Это напечатает Hello, World!
в консоли.
Пример 3: Замыкание, которое принимает параметр
let simpleClosure: (String) -> () = {имя в
печать (имя)
}
simpleClosure ("Привет, мир")
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, мир!
В приведенной выше программе тип закрытия (String) -> ()
указывает, что закрытие принимает входные данные типа String
, но не возвращает значение.Вы можете использовать значение, переданное внутри операторов закрытия, поместив имя параметра , имя , за которым следует в ключевом слове
.
Помните, что использование в ключевом слове
заключается в разделении имени параметра с операторами. Поскольку замыкание принимает String
, вам нужно передать строку, пока вы вызываете замыкание как simpleClosure ("Hello, World")
. Это выполняет оператор внутри замыкания и выводит Hello, World! в консоли.
Пример 4: Замыкание, возвращающее значение
Замыкание также может возвращать значение в виде функций. Если вам нужно вернуть значение из закрытия, вы должны явно добавить тип для возврата внутри фигурных скобок ()
, за которым следует ->
.
let simpleClosure: (String) -> (String) = {имя в
let welcome = "Hello, World!" + "Программа"
ответное приветствие
}
let result = simpleClosure ("Привет, мир")
печать (результат)
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, мир! Программа
В приведенной выше программе мы определили тип как simpleClosure: (String) -> (String)
, потому что Swift не может автоматически вывести замыкание, которое возвращает значение.Тип (String) -> (String)
указывает, что закрытие принимает входные данные типа String
, а также возвращает значение типа String
.
Замыкание также возвращает значение с использованием ключевого слова return как , возвращающее приветствие
, а возвращаемое значение может быть присвоено в переменной / константе как let result =
, как мы узнали в Swift functions .
Пример 4: Передача замыканий в качестве параметра функции
Мы также можем передать закрытие в качестве параметра функции как:
func someSimpleFunction (someClosure: () -> ()) {
print ("Функция вызывается")
}
someSimpleFunction (someClosure: {
print ("Привет, мир! от закрытия")
})
Когда вы запустите вышеуказанную программу, вывод будет:
Функция вызывается
В приведенной выше программе функция принимает замыкание типа () -> ()
i.e не вводит и не возвращает никакого значения.
Теперь при вызове функции someSimpleFunction ()
вы можете передать закрытие {print ("Hello World! From closure")}
в качестве параметра.
Вызов функции запускает оператор print ("Function Called")
внутри функции, который выводит на экран Function Called . Однако оператор закрытия не выполняется, потому что вы не выполняли вызов закрытия.
Замыкание можно вызвать просто как someClosure ()
, который выполняет оператор внутри замыкания.
func someSimpleFunction (someClosure: () -> ()) {
print ("Функция вызывается")
someClosure ()
}
someSimpleFunction (someClosure: {
print ("Привет, мир! от закрытия")
})
Когда вы запустите вышеуказанную программу, вывод будет:
Функция вызывается Привет, мир! от закрытия
Подвижное закрытие
Если функция принимает закрытие в качестве последнего параметра, закрытие может быть передано аналогично телу функции между {}
.Этот тип замыкания, записанный вне скобок вызова функции, известен как завершающее замыкание.
Вышеупомянутая программа может быть переписана с использованием конечного закрытия как:
Пример 5: скользящее закрытие
func someSimpleFunction (msg: String, someClosure: () -> ()) {
печать (сообщение)
someClosure ()
}
someSimpleFunction (msg: "Hello Swift Community!") {
print ("Привет, мир! от закрытия")
}
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, сообщество Swift! Привет, мир! от закрытия
В приведенной выше программе функция someSimpleFunction ()
принимает закрытие в качестве последнего параметра.Итак, при вызове функции вместо передачи замыкания в качестве аргумента мы использовали завершающее замыкание.
Как видите, в вызове функции
someSimpleFunction (msg: "Hello Swift Community!") { print ("Привет, мир! от закрытия") }
закрытие {print ("Hello World! From closure")}
выглядит как тело функции, а не как аргумент функции, но помните, что это все еще аргумент функции.
Из-за завершающего замыкания мы не указали имя параметра для замыкания, что делает код короче и удобочитаемым.
Записывать завершающее замыкание не обязательно. Однако рекомендуется для удобства чтения, когда функция принимает закрытие в качестве последнего аргумента.
Автоматическое закрытие
Закрытие, помеченное ключевым словом @autoclosure
, называется автоматическим закрытием. @autoclosure
ключевое слово создает автоматическое закрытие вокруг выражения, добавляя {}
. Таким образом, вы можете опускать фигурные скобки {}
при передаче замыканий в функцию.
Основным преимуществом использования автозакрытия является то, что вам не нужно заключать выражение в фигурные скобки {}
при вызове замыканий.
Давайте посмотрим на это на примере.
Пример 6: Крышка без @autoclosure
func someSimpleFunction (someClosure: () -> (), msg: String) {
печать (сообщение)
someClosure ()
}
someSimpleFunction (someClosure: ({
print ("Привет, мир! от закрытия")
}), сообщение: "Hello Swift Community!")
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, сообщество Swift! Привет, мир! от закрытия
В приведенной выше программе мы объявили функцию, которая принимает нормальное закрытие () -> ()
в качестве параметра функции someSimpleFunction ()
. Как видите, при вызове функции вам нужно добавить
{}
вокруг параметра функции как
someClosure: ({ print ("Привет, мир! от закрытия") })
Мы можем переписать указанную выше программу, используя автоматическое замыкание, как:
Пример 7: Автозащита
func someSimpleFunction (someClosure: @autoclosure () -> (), msg: String) {
печать (сообщение)
someClosure ()
}
someSimpleFunction (someClosure: (print ("Привет, мир! от закрытия")), msg: "Привет, сообщество Swift!")
Когда вы запустите вышеуказанную программу, вывод будет:
Привет, сообщество Swift! Привет, мир! от закрытия
В приведенной выше программе мы пометили замыкание () -> ()
как автоматическое замыкание типа с атрибутом @autoclosure
.Таким образом, вам не нужно добавлять {}
вокруг параметра функции как someClosure: (print ("Hello World! From closure"))
.
Автоматическое замыкание с аргументами и возвращаемым значением
Как и обычные замыкания, вы можете передавать аргументы и возвращать значение из автоматического замыкания. Однако даже если вы передадите аргументы, они будут проигнорированы и не могут использоваться внутри замыкания. Это потому, что вы не можете определить параметр, чтобы использовать его как {arg in}
.
Таким образом, рекомендуется создавать автозакрытия, которые не принимают аргументов, но могут возвращать значение.Значение — это выражение, заключенное в него. Давайте посмотрим на это на примере ниже.
Пример 8: Автоматическое замыкание с возвращаемым значением
func someSimpleFunction (_ someClosure: @autoclosure () -> (String)) {
пусть res = someClosure ()
печать (разрешение)
}
someSimpleFunction ("Доброе утро")
Когда вы запустите вышеуказанную программу, вывод будет:
Доброе утро
В приведенной выше программе мы определили функцию, которая не принимает параметров, но возвращает String
( () -> (String)
). Сдали в функцию автокож «Доброе утро» . Это также возвращаемое значение закрытия.
Итак, когда мы вызвали someClosure ()
внутри функции, она вернула значение Good Morning .
Пример 9: Автозащита с аргументами
func someSimpleFunction (_ someClosure: @autoclosure (String) -> (String)) {
let res = someClosure ("Привет, мир")
печать (разрешение)
}
someSimpleFunction ("Доброе утро")
Когда вы запустите вышеуказанную программу, вывод будет:
Доброе утро
В приведенной выше программе мы определили функцию, которая принимает автоматическое закрытие.Замыкание принимает значение типа String
, а также возвращает значение типа String
.
Как и в предыдущем примере, мы передаем функцию autoclosure «Good Morning» , которая является возвращаемым значением закрытия.
Таким образом, даже если автоматическое закрытие называется someClosure («Hello World»)
, оно не может принимать какие-либо параметры, оно все равно возвращается и печатает Good Morning .
Экранирование против замыканий без выхода
Закрытие без выхода
Говорят, что замыкание не имеет выхода, если замыкание передается в качестве аргумента функции и вызывается перед возвратом функции.Замыкание не используется вне функции.
В Swift все параметры закрытия по умолчанию не экранируются. Концепция выхода и закрытия закрытия предназначена для оптимизации компилятора.
Пример 10: Нет выхода из закрытия
func testFunctionWithNoEscapingClosure (myClosure :() -> Void) {
print ("функция вызвана")
myClosure ()
возвращаться
}
// вызов функции
testFunctionWithNoEscapingClosure {
print ("закрытие вызвано")
}
Когда вы запустите вышеуказанную программу, вывод будет:
функция вызвана закрытие называется
В приведенном выше примере сказано, что закрытие не является экранирующим, потому что закрытие myClosure ()
вызывается перед возвратом функции, и закрытие не используется вне тела функции.
Выходное закрытие
Говорят, что замыкание экранирует функцию, когда замыкание передается в качестве аргумента функции, но вызывается после возврата из функции или использования замыкания вне тела функции.
Пример 11: Выход из закрытия
var closureArr: [() -> ()] = []
func testFunctionWithEscapingClosure (myClosure: @escaping () -> Void) {
print ("функция вызвана")
closureArr.append (myClosure)
myClosure ()
возвращаться
}
testFunctionWithEscapingClosure {
print ("закрытие вызвано")
}
Когда вы запустите вышеуказанную программу, вывод будет:
функция вызвана закрытие называется
В приведенном выше примере мы объявили переменную closureArr , которая может хранить массив замыканий типа () -> ()
.
Теперь, если мы добавим закрытие myClosure , определенное в рамках функции, к закрытию closureArr , определенному вне функции, закрытие myClosure должно выйти из тела функции.
Итак, замыкание должно быть экранированным и помечено ключевым словом @escaping
.
Пример 12: Нет выхода из закрытия
В приведенном выше разделе «Закрытие без экранирования» мы описали, что замыкание не должно быть экранированием, если замыкание вызывается перед возвратом функции.Итак, если закрытие вызывает после возврата функции, это должно быть экранировано, верно?
Давайте проверим это на примере:
func testFunctionWithNoEscapingClosure (myClosure :() -> Void) {
возвращаться
myClosure ()
}
Приведенный выше код возвращает предупреждение, поскольку операторы, появляющиеся после оператора return (в нашем случае myClosure ()
), не выполняются, потому что оператор return передает управление программой вызывающей функции.
Итак, как мы протестируем, чтобы закрытие вызывалось после возврата из функции. Это может быть сделано, если вызов закрытия помещен в асинхронную операцию.
Синхронная операция ожидает завершения / завершения операции перед переходом к следующему оператору (порядок сверху вниз). И асинхронный переход к следующему оператору, даже если текущая операция не завершена.
Следовательно, операторы, помещенные в асинхронную операцию, могут в какой-то момент выполняться позже.
func testFunctionWithNoEscapingClosure (myClosure: @escaping () -> Void) {
DispatchQueue.main.async {
myClosure ()
}
возвращаться
}
В приведенной выше программе DispatchQueue.main.async
запускает блок кода асинхронно. А сейчас. вызов закрытия myClosure ()
может произойти даже после возврата из функции. Итак, закрытие должно быть экранированным и помечено ключевым словом @escaping
.
Закрытие как обработчик завершения
Обработчик завершения — это обратные вызовы / уведомления, которые позволяют выполнять некоторые действия, когда функция завершает свою задачу.
Обработчик завершения в основном используется в асинхронных операциях, так что вызывающий может знать, когда операция завершена, чтобы выполнить какое-либо действие после завершения операции.
Пример 13: Закрытие как обработчик завершения
func doSomeWork (завершение: () -> ()) {
print ("функция вызвана")
print («здесь работают»)
print ("перед обратным вызовом")
завершение ()
print ("после обратного вызова")
}
doSomeWork (завершение: {
print ("обратный звонок получен")
})
print ("Другие утверждения")
Когда вы запустите вышеуказанную программу, вывод будет:
функция вызвана здесь работают перед вызовом обратного вызова обратный звонок получен после обратного вызова Прочие заявления
Вышеупомянутая программа также может быть переписана с использованием конечного замыкания как:
Пример 14: Завершение закрытия как обработчик завершения
func doSomeWork (завершение: () -> ()) {
print ("функция вызвана")
print («здесь работают»)
print ("перед обратным вызовом")
завершение ()
print ("после обратного вызова")
}
поработай немного() {
print ("обратный звонок получен")
}
print ("Другие утверждения")
Как работает обработчик завершения?
Вот шаги выполнения:
-
doSomeWork ()
вызывает функцию, которая выполняет оператор внутри функции -
print ("функция вызвана")
выводит функцию, которая называется в консоли. - Вы можете выполнять некоторую работу внутри функции. На данный момент только оператор
print («здесь работает»)
, который выводит , некоторые работают здесь в консоли. - Простой вызов закрытия как
Завершение ()
отправит обратный вызов и передаст управление программой операторам внутри закрытия.
Итак,print («обратный вызов принят») выполняет
, который выводит обратного вызова, полученного в консоли. - После этого программное управление снова возвращается к вызову закрытия и выполняет оператор
print («после вызова обратного вызова»)
, который выводит после вызова обратного вызова в консоли. - После выполнения операторов внутри функции программа передает управление вызову функции
doSomeWork ()
, а затем выполняет следующий операторprint («Другие операторы»)
, который выводит Другие операторы в консоль.
Рассмотрим еще один практический пример использования замыкания в качестве обработчика завершения.
Пример 11: Закрытие как обработчик завершения
переменная closeButtonPressed = false
func codeinPlayground (завершение: (String) -> ()) {
print («Код, вздремни и расслабься»)
if closeButtonPressed {
завершение («Закройте детскую площадку»)
}
}
codeinPlayground {(msg) в
печать (сообщение)
}
Когда вы запустите вышеуказанную программу, вывод будет:
Код, вздремни и расслабьсяВ приведенной выше программе мы объявили переменную closeButtonPressed , которая имитирует нажатие пользователем кнопки закрытия на игровой площадке.Подумайте, если вы нажмете кнопку закрытия, переменная closeButtonPressed будет
true
.Функция
codeinPlayground
принимает закрытие в качестве аргумента. Завершениезавершения
принимает строку, но не возвращает значение.
Так как
closeButtonPressed
присвоеноfalse
, оператор внутри оператора if не выполняется и закрытие не вызывается.Теперь, если вы присвоите closeButtonPressed значение
true
(т.е.е., когда пользователь нажал кнопку закрытия) какvar closeButtonPressed = true
, операторы внутри if выполняются и вызывается закрытие.Когда вы присваиваете true переменной closeButtonPressed , вывод будет:
Код , вздремни и расслабься Закройте детскую площадкуЗдесь мы использовали закрытие в качестве обработчика завершения, потому что, когда пользователь нажимает кнопку закрытия, мы не хотим выполнять операторы внутри функции
codeinPlayground
, вместо этого завершаем ее выполнение, вызывая завершение закрытия(«Закройте игровую площадку» )
.Таким образом, мы получаем возможность обрабатывать все финальные события, связанные с функцией, внутри операторов закрытия.
В нашем случае мы выводим сообщение в консоль как
print (msg)
.
Укупорочные средства | Swift by Sundell
Подобно функциям, замыкания позволяют нам определять группу операторов кода, которые могут вызываться как одно целое, которое может принимать как ввод, так и производить вывод. Разница, однако, в том, что замыкания могут быть определены в строке — прямо в том месте, где мы хотим их использовать, — что невероятно полезно во многих различных ситуациях.
Замыкания могут храниться как свойства и локальные переменные, а также могут передаваться как аргументы функциям (или другим замыканиям), например:
struct IntToStringConverter {
var body: (Int) -> Строка
}
let intProvider: () -> Int = {return 7}
func performOperation (затем закрытие: () -> Void) {
...
}
Чтобы посмотреть, как замыкания могут быть действительно полезны на практике, предположим, что мы хотели расширить тип Swift String
функцией, которая позволяет нам преобразовывать каждое слово, которое появляется в строке. Используя закрытие, мы можем позволить вызывающим объектам нашей новой функции свободно решать , как именно выполнять каждое преобразование:
extension String {
func transformWords (
используя закрытие: (Подстрока) -> Строка
) -> String {
пусть слова = разделить (разделитель: "")
var results = [String] ()
for word in words {
пусть трансформируется = закрытие (слово)
results.append (преобразованный)
}
вернуть результаты.присоединился (разделитель: "")
}
}
Обратите внимание, что приведенный выше образец кода является всего лишь примером, так как это не очень эффективный способ преобразования строк. Чтобы узнать больше о строках и о том, как работает указанный выше тип Substring
, ознакомьтесь со статьей «Строки».
Теперь мы можем вызвать указанную выше функцию с любым замыканием, которое мы хотим, при условии, что она принимает Substring
в качестве входных данных и производит String
в качестве выходных. Например, вот как мы можем преобразовать каждое слово в строке в нижний регистр:
let string = "Hello, world!".transformWords (используя: {word in
вернуть word.lowercased ()
})
print (string)
Ключевое слово in
, которое мы использовали выше, среди прочего, используется для именования аргументов, которые принимает наше замыкание.
По сравнению с большинством других языковых функций синтаксис закрытия Swift довольно гибкий, и есть ряд функций, которыми мы можем воспользоваться, чтобы сделать приведенный выше код немного более компактным:
- Использование синтаксиса завершения , мы можем просто добавить наше закрытие к имени функции, которую мы вызываем, вместо того, чтобы вводить круглые скобки и метку параметра.
- Мы можем заменить
слова в
сокращением аргумента закрытия$ 0
, которое позволяет нам ссылаться на первый (и в нашем случае единственный) аргумент, переданный в закрытие. - Мы можем удалить ключевое слово
return
, которое не требуется для замыканий с одним выражением (функция, которая, начиная со Swift 5.1, также применяется к функциям и вычисляемым свойствам).
При применении всего вышеперечисленного наш код теперь будет выглядеть так:
let string = "Hello, world!".transformWords {$ 0.lowercased ()}
При использовании более компактного синтаксиса мы всегда должны быть осторожны, чтобы не слишком сильно ухудшить читаемость нашего кода — поэтому для более сложных замыканий использование более подробного варианта синтаксиса может быть более сложным. подходящее.
В качестве аргументов функции можно не только передавать вновь определенные замыкания, но и передавать существующее замыкание другой функции. Например, вот как мы могли бы вернуться к реализации нашей функции transformWords
и просто передать данное замыкание в map
— вместо того, чтобы писать итерацию вручную:
extension String {
func transformWords (
используя закрытие: (Подстрока) -> Строка
) -> String {
пусть слова = разделить (разделитель: "")
пусть результаты = слова.
карта (закрытие)
вернуть results.joined (разделитель: "")
}
}
Чтобы узнать больше о карте
, прочтите статью «Карта, FlatMap и CompactMap».
До сих пор мы использовали только замыкания, которые выполняются немедленно, а затем отбрасываются, но также очень распространено желание сохранить замыкание для дальнейшего использования. Например, предположим, что мы хотели написать функцию delay
, которая позволяет нам задерживать выполнение любого закрытия на определенное количество секунд.Для этого мы будем использовать API asyncAfter
от Grand Central Dispatch — однако, поскольку передача нашего закрытия этому API приведет к тому, что он будет храниться до тех пор, пока не пройдет наш интервал времени задержки, нам нужно будет пометить его как @escaping
, например:
задержка функции (по секундам: TimeInterval,
в очереди: DispatchQueue = .main,
закрытие: @escaping () -> Void) {
queue.
asyncAfter (
крайний срок: .now () + секунды,
выполнить: закрытие
)
}
Когда закрытие помечено как @escaping
, это означает, что оно может быть сохранено для дальнейшего использования, а также означает, что нам нужно явно использовать self
всякий раз, когда мы обращаемся к свойству экземпляра. или метод внутри него:
class ProfileViewController: UIViewController {
переопределить функцию viewWillAppear (_ animated: Bool) {
супер.viewWillAppear (анимированный)
задержка (на: 2) {
self.showTutorialIfNeeded ()
}
}
private func showTutorialIfNeeded () {
...
}
}
Однако есть кое-что, с чем нужно быть очень осторожными при написании кода, подобного приведенному выше, — это , захватывающий . Причина, по которой мы должны явно использовать self
в экранировании замыканий, заключается в том, что он вызывает захват этого объекта, что означает, что он будет сохраняться в памяти до тех пор, пока само замыкание остается в памяти, что может вызвать утечку памяти, если мы невнимательны.
Как мы рассмотрели в статье «Управление памятью» , один из способов предотвратить сильный захват — использовать список захвата , чтобы указать, что мы хотели бы захватить себя
слабо — что не приведет к его сильному удержанию:
delay (by: 2) {[weak self] in
сам? .showTutorialIfNeeded ()
}
Обратите внимание, что в приведенном выше примере на самом деле не имеет большого значения, если мы сильно захватываем self
, так как наше закрытие будет сохраняться только в течение 2 секунд.Однако, поскольку мы можем увеличить этот временной интервал в будущем, все еще может быть хорошей идеей использовать weak
— тем более, что в любом случае нет причин для сильного сохранения self
в этом случае.
Наконец, давайте посмотрим, как грань между функциями и замыканиями становится еще более размытой, когда мы начинаем использовать возможности первоклассных функций Swift. Возвращаясь к нашему предыдущему примеру преобразования слов, предположим, что мы определили стандартную функцию, которая использует любое переданное слово с большой буквы:
func capitalize (word: Substring) -> String {
ответное слово.заглавные
}
Поскольку Swift поддерживает функции первого класса, мы можем фактически передать указанную выше функцию так же, как если бы это было закрытие (Substring) -> String
, что, в свою очередь, позволяет нам использовать его в качестве аргумента при вызове нашего предыдущего преобразования .
, так как она принимает замыкание именно такой формы:
let name = "swift by sundell" .transformWords (using: capitalize)
print (name)
Довольно круто! Конечно, есть огромное количество других вещей, которые мы можем делать с замыканиями в Swift, некоторые из которых могут увести нас глубже в сферу функционального программирования, но я надеюсь, что эта статья дала вам краткий базовый обзор того, как замыкания могут быть используется в Swift.
Спасибо за чтение! 🚀
Emerge: Постоянно отслеживайте и уменьшайте размер приложения. Простые в использовании плагины Emerge для GitHub и fastlane автоматически просканируют двоичный файл вашего приложения и предоставят вам простые и действенные предложения о том, как сделать его меньше и, в свою очередь, быстрее для загрузки пользователями. Установите демо прямо сейчас!
Механизм захвата закрытия Swift | Swift от Sundell
Замыкания становятся все более важной частью Swift, как с точки зрения общего направления самого языка, так и когда дело доходит до способов, которыми Apple и сторонние разработчики создают библиотеки и API с его использованием.Однако замыкания также связаны с определенным набором сложностей и поведения, которые поначалу может быть довольно трудно полностью понять, особенно когда речь идет о том, как они захватывают значения и объекты из окружающего их контекста для выполнения своей работы.
В то время как мы уже рассмотрели различные способы захвата объектов в замыканиях в выпуске «Захват объектов в замыканиях Swift» за 2017 год, на этой неделе давайте рассмотрим концепцию захвата более широко — более подробно рассмотрев некоторые возможности и проблемы, связанные с написанием фиксации замыканий в целом.
Emerge: Постоянно отслеживайте и уменьшайте размер приложения. Простые в использовании плагины Emerge для GitHub и fastlane автоматически просканируют двоичный файл вашего приложения и предоставят вам простые и действенные предложения о том, как сделать его меньше и, в свою очередь, быстрее для загрузки пользователями. Установите демо прямо сейчас!
Каждый раз, когда мы определяем экранирующее замыкание , то есть замыкание, которое либо сохраняется в свойстве, либо захватывается другим экранирующим замыканием, оно неявно фиксирует любые объекты, значения и функции, на которые в нем есть ссылки.Поскольку такие закрытия могут быть выполнены позже, они должны поддерживать сильные ссылки на все свои зависимости, чтобы предотвратить их освобождение в то же время.
Например, здесь мы используем Grand Central Dispatch , чтобы задержать представление UIAlertController
на три секунды, для чего требуется закрытие, переданное в вызов asyncAfter
, чтобы захватить экземпляр контроллера представления presenter
:
func presentDelayedConfirmation (в презентаторе: UIViewController) {
let queue = DispatchQueue.
главный
queue.asyncAfter (крайний срок: .now () + 3) {
let alert = UIAlertController (
заглавие: "...",
сообщение: "...",
предпочтительный стиль: .alert
)
presenter.present (оповещение, анимация: true)
}
}
Хотя описанное выше поведение действительно удобно, оно также может стать источником некоторых действительно сложных ошибок и проблем, связанных с памятью, если мы не будем осторожны.
Например, поскольку мы задерживаем выполнение приведенного выше кода на несколько секунд, возможно, что наш контроллер представления presenter
будет удален из иерархии представлений нашего приложения к тому моменту, когда закрытие действительно запускается — и пока в данном случае это не было бы катастрофой, возможно, для нас было бы лучше представить наше подтверждение только в том случае, если контроллер представления все еще удерживается другим объектом (предположительно его родительским контроллером представления или окном).
Здесь появляются списки захвата , которые позволяют нам настроить, как данное замыкание захватывает любые объекты или значения, на которые оно ссылается. Используя список захвата, мы можем проинструктировать наше вышеупомянутое закрытие захватить presenter
view controller слабо , а не строго (что по умолчанию). Таким образом, контроллер представления будет освобожден, если на него не будет ссылаться какая-либо другая часть нашей базы кода, в результате чего память будет освобождаться быстрее и не будут выполняться ненужные операции:
func presentDelayedConfirmation (в презентаторе: UIViewController) {
let queue = DispatchQueue.главный
queue.asyncAfter (крайний срок: .now () + 3) {[слабый докладчик] в
охранник let presenter = presenter else {return}
let alert = UIAlertController (
заглавие: "...",
сообщение: "...",
предпочтительный стиль: .alert
)
presenter.
present (оповещение, анимация: true)
}
}
Списки захвата, возможно, даже более полезны, когда нам нужно ссылаться на self
, особенно когда это вызовет цикл сохранения , когда два объекта или замыкания ссылаются друг на друга, предотвращая их постоянное освобождаются (поскольку они не могут достичь нулевого счетчика ссылок).
Вот пример такой ситуации, в которой мы используем список захвата, чтобы избежать ссылки на self
строго внутри замыкания, которое также будет сохранено self
:
class UserModelController {
пусть хранилище: UserStorage
частный пользователь var: Пользователь {didSet {userDidChange ()}}
init (пользователь: Пользователь, хранилище: UserStorage) {
self.storage = хранилище
self.user = пользователь
storage.addObserver (forID: user.id) {[слабый я] пользователь в
себя?.пользователь = пользователь
}
}
}
В качестве альтернативы мы могли бы преобразовать указанное выше свойство user
в функцию, используя его ключевой путь, как мы сделали в «Возможности ключевых путей в Swift» — поскольку все, что мы делаем, находится в пределах нашего наблюдения закрытие обновляет значение этого свойства.
Причина, по которой указанное выше закрытие в конечном итоге вызовет цикл сохранения, если мы не захватили self
слабо, заключается в том, что UserStorage
сохранит это закрытие, а self
уже сохраняет этот объект через свое свойство storage
.
Хотя из двух приведенных выше примеров кода может показаться, что всегда нужно фиксировать self
слабо, но это определенно не так. Как и в случае с другими видами управления памятью, нам необходимо тщательно продумать, как self
будет использоваться в каждой ситуации и как долго мы ожидаем, что каждое захватывающее замыкание будет оставаться в памяти.
Например, если мы имеем дело с действительно кратковременными замыканиями, такими как те, которые передаются в UIView.animate
API (которые просто выполняются для выполнения интерполяции для анимации, а затем освобождаются), захват self
на самом деле не проблема и, скорее всего, приведет к написанию кода, который будет легче читать:
extension ProductViewController {
func expandImageView () {
UIView.
animate (withDuration: 0,3) {
self.imageView.frame = self.view.bounds
self.showImageCloseButton ()
}
}
}
Обратите внимание на то, что нам всегда нужно явно ссылаться на self
при доступе как к методам экземпляра, так и к свойствам внутри экранирующего замыкания. Это хорошо, так как требует от нас принятия явного решения о захвате и
с учетом возможных последствий.
Есть также много видов ситуаций, в которых может захотеть, чтобы сохранял self
даже дольше — например, если текущий объект требуется для выполнения работы закрытия, как в этом случае:
extension NetworkingController {
func makeImageUploadingTask (для изображения: Image) -> Task {
Задача {обработчик в
пусть запрос = Запрос (
конечная точка:.imageUpload,
полезная нагрузка: изображение
)
self.perform (запрос, затем: обработчик)
}
}
}
Приведенный выше код не вызовет никаких циклов сохранения, поскольку NetworkingController
не сохраняет задачи, которые он создает. Чтобы узнать о более сложных способах моделирования и работы с задачами в Swift, ознакомьтесь с разделом «Параллелизм на основе задач в Swift».
Мы также можем захватить каждую из зависимостей замыкания напрямую, вместо того, чтобы ссылаться на self
— опять же, используя список захвата.Например, здесь мы захватываем свойство cache
загрузчика изображений, чтобы иметь возможность использовать его после успешной загрузки изображения:
class ImageLoader {
private let cache = Cache ()
func loadImage (
из url: URL,
затем обработчик: @escaping (Result ) -> Void
) {
запрос (URL) {[кеш] результат в
делать {
let image = попробуйте result.decodedAsImage ()
кеш.вставить (изображение, forKey: url)
обработчик (.success (изображение))
} ловить {
обработчик (.failure (error))
}
}
}
}
Вышеупомянутый метод работает очень хорошо, когда нам нужен доступ только к нескольким нашим свойствам, а не к self
в целом — если эти свойства либо содержат ссылочные типы (экземпляры классов), либо неизменяемые типы значений. .
Типы значений иногда могут быть немного сложнее, когда дело доходит до захвата замыкания, поскольку они передаются во внешние области как копии, а не как ссылки.Хотя именно это и делает типы значений Swift такими мощными, это может иметь несколько неожиданные последствия в ситуациях, подобных приведенной ниже — в которой мы захватываем свойство отправителя
и message
при назначении обработчика замыкания
кнопке:
class MessageComposerViewController: UIViewController {
частный отправитель: MessageSender
личное сообщение var = Сообщение ()
частный ленивый var sendButton = ActionButton ()
...
переопределить функцию viewDidLoad () {
super.viewDidLoad ()
...
sendButton.handler = {[отправитель, сообщение] в
sender.send (сообщение)
}
}
}
На первый взгляд приведенный выше код может показаться прекрасным. Однако тип Message
, который мы использовали выше, реализован в виде структуры, которая придает ему семантику значений — это означает, что мы просто захватываем его текущее значение при добавлении его в наш список захвата. Таким образом, даже если это значение может измениться в течение жизненного цикла нашего контроллера представления, после нажатия на наш
sendButton
мы все равно отправим исходное значение, что не очень хорошо.
Один из способов решить указанную выше проблему, избегая при этом дополнительных операторов guard
, — это захватить только себя
, чтобы иметь возможность получить доступ к его сообщению
, а затем сопоставить это значение непосредственно с отправителя, отправить
метод, например:
sendButton.handler = {[weak self, sender] в
let message = self? .message
message.map (sender.send)
}
Однако вышесказанное действительно является проблемой только при работе с изменяемыми значениями.Если вместо этого у нас есть только константы, как в следующем примере, то мы можем без проблем добавить эти свойства в любой список захвата замыкания (поскольку их значения не изменятся):
class ProductViewController: UIViewController {
приватный let productManager: ProductManager
частная аренда продукт: продукт
.
..
переопределить функцию viewDidLoad () {
super.viewDidLoad ()
...
buyButton.handler = {[productManager, product] в
productManager.startCheckout (для: продукта)
}
}
}
В ситуациях, подобных описанной выше, мы также могли бы создать новую функцию, объединив наше значение (, продукт
в данном случае) с методом, которому оно будет передано. Посмотрите первый выпуск Swift Clips, чтобы увидеть пример этого.
Наконец, давайте посмотрим, как собираются значения, когда дело доходит до локальных переменных . В отличие от того, как захватываются свойства, основанные на значениях, локальные переменные по-прежнему поддерживают связь с их исходным объявлением при захвате замыканием в той же области , что может быть невероятно полезно для отслеживания различных видов состояния.
Например, предположим, что мы хотели расширить протокол Swift Collection
с помощью API, чтобы мы могли перебирать любую коллекцию, используя буфер, состоящий из текущего и следующего элементов. Это можно сделать, объединив типы стандартной библиотеки
AnySequence
и AnyIterator
с локально захваченными значениями, например:
extension Collection {
typealias Buffer = (текущий: элемент, следующий: элемент?)
var buffered: AnySequence <Буфер> {
AnySequence {() -> AnyIterator в
var iterator = self.makeIterator ()
var next: Element?
return AnyIterator {() -> Buffer? в
охранник пусть текущий = следующий ?? iterator.next () else {
вернуть ноль
}
next = iterator.next ()
возврат (текущий, следующий)
}
}
}
}
Для получения дополнительной информации о вышеупомянутом способе создания пользовательских последовательностей ознакомьтесь с разделом «Перенос последовательностей в Swift».
Таким образом, значения копируют при захвате с использованием списка захвата, в то время как они не копируются при непосредственной ссылке — например, при доступе к свойствам или когда локальная переменная захватывается в той же области, в которой она была определена в.
Последний вариант, когда дело доходит до захвата замыкания, — использовать незанятых
ссылок. Они, как и слабых
ссылок, указываются с помощью списков захвата — и также могут применяться только к ссылочным типам. Использование unowned
дает нам по существу тот же результат, что и при использовании принудительно развернутых опций, поскольку позволяет нам обрабатывать слабую ссылку, как если бы она была необязательной, но приведет к сбою, если мы попытаемся получить к ней доступ после того, как она была освобожден.
Возвращаясь к нашему предыдущему примеру UserModelController
, вот как бы он выглядел, если бы мы использовали unowned
вместо weak
:
class UserModelController {
...
init (пользователь: Пользователь, хранилище: UserStorage) {
...
storage.addObserver (forID: user.id) {[unowned self] пользователь в
self.user = пользователь
}
}
}
Хотя использование unowned
позволяет нам избавиться от необязательных параметров и иногда может быть очень удобным, тот факт, что он вызывает сбои для освобожденных ссылок, делает его довольно опасным в использовании, если мы не абсолютно уверены, что данное закрытие победило ‘ t случайно запускается после освобождения одной из его зависимостей.
Однако одним из преимуществ таких сбоев является то, что они позволяют нам идентифицировать пути кода, которые в идеале никогда не должны были вводиться. Например, если вышеупомянутое закрытие наблюдения срабатывает после того, как self
был освобожден, это, вероятно, означает, что мы не отменяем регистрацию наших наблюдений должным образом, о чем было бы здорово знать.
Однако вместо того, чтобы использовать unowned
, мы могли бы (в этом случае) добиться того же самого, используя assert
— и хотя это приведет к немного большему количеству кода, это также даст нам гораздо больше действенное сообщение об ошибке в случае сбоя, и мы не вызовем сбоев в производстве:
class UserModelController {
...
init (пользователь: Пользователь, хранилище: UserStorage) {
...
storage.addObserver (forID: user.id) {[слабый я] пользователь в
assert (self! = nil, "" "
Похоже, UserModelController не отменил регистрацию \
сам как наблюдатель хранилища перед освобождением
"" ")
сам? .
user = пользователь
}
}
}
Чтобы узнать больше о assert
, а также о других способах распространения различных ошибок, ознакомьтесь с разделом «Выбор правильного способа отказа в Swift».
Emerge: Постоянно отслеживайте и уменьшайте размер приложения. Простые в использовании плагины Emerge для GitHub и fastlane автоматически просканируют двоичный файл вашего приложения и предоставят вам простые и действенные предложения о том, как сделать его меньше и, в свою очередь, быстрее для загрузки пользователями. Установите демо прямо сейчас!
Хотя модель управления памятью Swift с автоматическим подсчетом ссылок не требует от нас выделения и освобождения памяти вручную, она все же требует, чтобы мы точно решили, как мы хотим ссылаться на наши различные объекты и значения.
Хотя часто можно услышать чрезмерно упрощенные правила, такие как «Всегда использовать слабых
ссылок в замыканиях» , написание хорошо работающих и предсказуемых приложений и систем часто требует немного более тонкого мышления, чем это. Как и в случае с большинством вещей в мире разработки программного обеспечения, лучший подход, как правило, состоит в том, чтобы полностью изучить лежащие в основе механизмы и поведения, а затем выбрать, как их применять в каждой конкретной ситуации.
Надеюсь, эта статья дала некоторое представление об этих механизмах и поведении, когда дело доходит до захвата замыканий, и если у вас есть какие-либо вопросы, комментарии или отзывы — просто дайте мне знать в Twitter или по электронной почте.
Спасибо за чтение! 🚀
бесплатный учебник по взлому Swift
До сих пор вы встречали целые числа, строки, числа с плавающей запятой, логические значения, массивы, словари, структуры и классы, но есть другой тип данных, который широко используется в Swift, и он называется закрытием. Они сложные, но они настолько мощные и выразительные, что они повсеместно используются в Cocoa Touch, так что вы не уйдете очень далеко, не поняв их.
Замыкание можно рассматривать как переменную, содержащую код. Итак, если целое число содержит 0 или 500, закрытие содержит строки кода Swift. Замыкания также захватывают среду, в которой они созданы, что означает, что они берут копию значений, которые используются внутри них.
Вам никогда не понадобится для разработки собственных укупорочных средств, поэтому не бойтесь, если вам покажется следующее довольно сложным. Однако и Cocoa, и Cocoa Touch часто просят вас написать замыкания в соответствии с их потребностями, поэтому вам, по крайней мере, нужно знать, как они работают. Давайте сначала возьмем пример Cocoa Touch:
пусть vw = UIView ()
UIView.animate (withDuration: 0,5, анимация: {
vw.alpha = 0
})
UIView
— это тип данных iOS в UIKit, который представляет собой базовый тип контейнера пользовательского интерфейса. Не беспокойтесь о том, что он сейчас делает, важно только то, что это основной компонент пользовательского интерфейса. UIView
имеет метод под названием animate ()
, который позволяет вам изменять внешний вид вашего интерфейса с помощью анимации — вы описываете, что меняется и сколько секунд, а Cocoa Touch сделает все остальное.
Метод animate ()
принимает в этом коде два параметра: количество секунд для анимации и закрытие, содержащее код, который будет выполняться как часть анимации. Я указал полсекунды в качестве первого параметра, а для второго я попросил UIKit отрегулировать альфа-канал представления (непрозрачность) на 0, что означает «полностью прозрачный».
Этот метод должен использовать закрытие, потому что UIKit должен выполнять всевозможную работу, чтобы подготовиться к началу анимации, поэтому происходит то, что UIKit берет копию кода внутри фигурных скобок (это наше закрытие), сохраняет ее, выполняет всю подготовительную работу, а затем запускает наш код, когда он будет готов.Это было бы невозможно, если бы мы просто запускали наш код напрямую.
Приведенный выше код также показывает, как замыкания захватывают свое окружение: я объявил константу vw
вне замыкания, а затем использовал ее внутри. Swift обнаруживает это и также делает эти данные доступными внутри закрытия.
Swift автоматического захвата среды замыкания очень полезна, но может иногда сбивать вас с толку: если объект A хранит замыкание как свойство, и это свойство также ссылается на объект A, у вас есть нечто, называемое циклом сильных ссылок, и вы есть недовольные пользователи.Это значительно более сложная тема, чем вам нужно знать прямо сейчас, так что пока особо не беспокойтесь об этом.
Трейлинг закрытия
Поскольку замыкания используются так часто, Swift может добавить немного синтаксического сахара, чтобы облегчить чтение кода. Правило таково: если последний параметр метода принимает замыкание, вы можете исключить этот параметр и вместо этого предоставить его как блок кода в фигурных скобках. Например, мы можем преобразовать предыдущий код в этот:
пусть vw = UIView ()
UIView.animate (withDuration: 0,5) {
vw.alpha = 0
}
Это делает ваш код короче и легче читается, поэтому эта форма синтаксиса, известная как синтаксис замыкающего закрытия, является предпочтительной.
Спонсируйте взлом со Swift и войдите в крупнейшее в мире сообщество Swift!
закрытий в Swift. Я изучаю Swift последний раз… | Анджела Муго | The Andela Way
Я изучаю Swift последние девять месяцев. Сказать, что я в восторге, — значит ничего не сказать.Однако, когда я устраиваю быструю вечеринку, на улице останавливается полицейская машина. О закрытие, кто тебя обидел? Итак, я, наконец, решил изучить замыкания и решил задокументировать свои борьбы , обучения и, наконец, Ага моментов . Большую часть этого я узнал из разных источников, но обнаружил, что мне очень жаль, что я не нашел их в одном месте. К ним относятся, помимо прочего, блог разработчика Боба, быстрая документация и эта замечательная статья на Medium.
Почему такое название?
Замыкания могут захватывать и сохранять ссылки на любые константы и переменные из контекста, в котором они определены. Это называется закрытием над ними, отсюда и название закрытия clo.
Вы выглядите знакомо, где я вас раньше видел?
Замыкания встречаются во многих местах из-за их чистого и оптимизированного синтаксиса. Возможно, вы встречали их, когда;
- Использование анимации; это потому, что некоторые функции выполняются асинхронно. Это прекрасная возможность использовать
, избегая замыканий
. - Получение данных из стороннего API; это также еще одна отличная возможность использовать
, избегая замыканий
. - Передача данных между контроллерами представлений
Некоторые встроенные функции Swift требуют закрытия. Вот один;
Функции для замыканий
Apple определяет замыкания как автономные блоки функций, которые передаются. Прежде чем мы углубимся в технические детали, мы могли бы увидеть, как преобразовать функцию в замыкание.
Вот простая функция, которую я использую, чтобы узнать, сколько времени мне нужно на изучение замыканий.Он принимает два параметра: дней,
и , имя
, затем возвращает строку, которая сообщает мне, сколько дней у меня осталось. Классно, да? 😃
Преобразовать в укупорку;
Конечно, если вы попытаетесь запустить приведенный выше фрагмент как есть, вы получите сообщение об ошибке. Это что-то среднее между замыканием и функцией, возможно, мы могли бы назвать это забавой. Нет? Хорошо! 😄
- Добавьте
в ключевое слово
между списком аргументов и телом, то есть междуString
иверните
Ошибка все еще есть.
- Хотя это немного спорного заявления с большим количеством мнений и взглядов, закрытие, по существу, безголовые функции. Поэтому вынимаем головку 😈. В этом случае голова
func learnClosures
Все еще не запускается ??
- Обведите фигурными скобками
Почти готово 🤞. Это то, что называется закрывающим выражением в наиболее подробной форме . Как мы увидим позже, вы можете сократить его до одной строки.
- Итак, мы, по сути, объявили закрытие, но не можем его вызвать.
Решение? мы присваиваем его переменной. Это практически возможно, потому что укупорочные средства относятся к категории первого класса. Это также верно для функций .
Здесь у нас есть закрытие в его полной форме.
Определение замыканий
Как и функции, замыкания могут принимать разные формы;
- Замыкания, которые не принимают никаких параметров и ничего не возвращают
В Swift void
совпадает с ()
.Как правило, для замыканий необходимо указать возврат типа . Вышеупомянутое закрытие не принимает без параметра , а ничего не возвращает .
Мы можем упростить это до менее подробным ;
Что мы сделали?
- Мы извлекаем из объявления тип , это потому, что замыкание может вывести тип из контекста. Затем мы идем дальше и удаляем возвращаемый тип.
Вот почему говорят, что закрывающие выражения могут определять параметр и тип возвращаемого значения из контекста .
- Мы также удалили
из ключевого слова
, это потому, что с удалением параметров у нас нет головы , чтобы отделиться от тела
- Замыкания, которые принимают параметр и ничего не возвращают
Замыкание принимает один параметр строку
и ничего не возвращает.
Опять же, мы можем сократить это до
Что мы сделали?
- Мы удаляем имя аргумента и используем то, которое Swift автоматически генерирует.
- Каждому аргументу назначается имя в Swift.Допустим, у нас есть три параметра, назовем их (a, b, c). Swift назначит их (0, 1, 2 доллара).
- Это еще одна возможность передачи закрывающих выражений, известная как присвоение сокращенных имен аргументов.
- Замыкания, которые принимают параметры и возвращают данные
Замыкание принимает один параметр oneString
типа строка возвращает строку
.
Вы знаете, что за упражнение, ни у кого нет времени набирать такие подробные закрытия !!!
Вызов замыканий
Замыкания можно использовать в функциях
Возврат замыкания из функции
Мы рассмотрим два примера;
- Использование закрывающего выражения
2. Использование вложенной функции ; как мы увидим, вложенные функции — это особый случай замыканий
На заметку
Замыкания существуют в трех основных формах
- Глобальные функции ; это замыкания с именем, которое не захватывает окружающие их значения
- Вложенная функция с; замыкания с именем, которое захватывает значения включающей их функции
- закрывающих выражений ; безымянные замыкания, которые захватывают значения окружающего их контекста
- Как и функции , параметры замыкания являются константами и не могут быть изменены, , если они не отмечены как i n-out.
Однако, как и в функциях, вы можете пометить его как in-out
и поработать над этой ошибкой.
- Замыкания захватывают переменные окружающего контекста
Здесь мы обратимся к примеру вложенной функции.
Глядя на приведенные выше фрагменты, они могут оправдать необходимость закрытия. В первом примере, когда мы используем закрытие вложенной функции, она захватывает значение переменной a
и продолжает использовать его еще долго после завершения определения области действия.Вот почему всякий раз, когда мы вызываем его, он уже имеет значение и увеличивает его оттуда.
Однако при использовании простой функции теряется значение переменной, и мы начинаем заново каждый раз, когда вызываем функцию.
- Замыкания имеют ссылочный тип
Ароматизаторы / типы функций
Они используются, когда у вас есть замыкание в качестве последнего параметра в функции и оно слишком длинное для использования в качестве встроенного замыкания.
Экранирующие замыкания
Экранирующие замыкания имеют перед собой слово @escaping
, а неэкранирующие замыкания — нет.😆 Не достаточно хорошее определение? позвольте мне сделать еще одну попытку, объяснив их жизненный цикл.
В выходящем закрытии жизненный цикл выглядит так;
- закрыть проход в аргументе функции
- Выполнить некоторую задачу в функции
- вернуть компилятор
- закрыть асинхронно
Это означает, что закрытие переживает функцию, то есть вызывается после того, как функция вернулась. Примером этого является обработчик завершения ; закрытие, которое запускается только после завершения определенной задачи.
Сценарии использования;
Экранирующие замыкания используются в асинхронном программировании . Возможно, вам стоит взглянуть на многопоточность, если вы не знаете о ней в этом замечательном руководстве. В многопоточности мы используем несколько очередей. Когда используется экранирующее закрытие, очередь удерживает закрытие и запускает его только тогда, когда очередь завершена.
Экранирующие замыкания также используются при доступе к глобальным переменным. Это потому, что переменные переживают закрытие.
В приведенном выше фрагменте у нас есть замыкание, которое изменяет глобальную переменную.У него должен быть доступ к этой переменной, который не входит в область действия вызывающей функции и, следовательно, должен быть экранирован.
До Swift3 все закрытия экранировались, если явно не указано, что это noescape
. Однако из-за управления памятью теперь вы должны указать, когда вы хотите, чтобы закрытие сбегало.
LifeCycle без выхода закрытия;
- Закрытие прохода в аргументе функций
- Выполните некоторую работу в функции
- Закрытие выполнения
- Возврат компилятора назад
Как вы заметите, неэкранирующее закрытие не переживает свою вызывающую функцию.
Заключение
- До сих пор я использовал замыкания при создании анимации, используя многопоточность и выборку данных из внешних API. Я с нетерпением жду возможности использовать их для настройки представлений, например кнопок, написания более чистого кода в функциях и, что наиболее важно, для привязки компонентов при использовании MVVM.
2. Отлично! Мы знаем все это о закрытии, , следующие шаги ?
- Изучая замыкания, я натолкнулся на управление памятью как на основную причину, по которой мы используем экранирование замыканий.Я бы порекомендовал документацию Apple, чтобы узнать больше об этом.
- Лично я обнаружил, что закрытие не происходит естественным образом, и я полагаю, что то же самое и со многими другими разработчиками. Я бы посоветовал вам выработать привычку читать код других опытных разработчиков, чтобы увидеть, как они используют замыкания.
- Было бы полезно взглянуть на встроенные функции Swift, у которых есть замыкания, когда вы встретите их.
3. Можете ли вы помочь? Я намеренно не разъяснял, что замыкания являются ссылочными типами .Это потому, что, честно говоря, я сделал , но не очень хорошо понял . Вы закрывает Ninja ? Можете ли вы помочь ? Я бы с удовольствием пообщался в комментариях к тому же 😃.
Functional Swift: закрытие {}. Демистифицируем @escaping, @ non-escape… | by Aaina jain | Swift India
Демистификация @escaping, @ non-escaping, @autoclosure и функция карри
Кредиты: PexelsЗамыкания — это автономные блоки функциональности, которые можно передавать и использовать в вашем коде.
— Apple
Замыкания могут захватывать и сохранять ссылки на любые константы и переменные из контекста, в котором они определены, известного как , закрывающего более , следовательно, закрытие. Вы можете думать о замыкании как о функции, которая не имеет собственного имени и захватывает любые значения из своей среды. Функции и замыкания — это первоклассных объектов в Swift: вы можете хранить их, передавать в качестве аргументов функциям и обращаться с ними, как с любым другим значением или объектом.Передача замыканий в качестве обработчиков завершения — распространенный шаблон во многих API. Стандартная библиотека Swift использует замыкания в основном для обработки событий и обратных вызовов.
Функции — это автономные фрагменты кода, которые выполняют определенную задачу. Вы даете функции имя, которое идентифицирует то, что она делает, и это имя используется для «вызова» функции для выполнения ее задачи при необходимости. Вы определяете функцию с помощью ключевого слова func
. Функции могут не принимать ни одного параметра ко многим параметрам, переменным параметрам и возвращать ни одного или несколько параметров.
F Тип функции: состоит из типов параметров и типа возвращаемого значения функции. В приведенном выше примере тип функции:
(Int, Int) -> Int
Это можно читать как: «Функция, которая имеет два параметра, оба типа Int
и возвращающие значение типа Int
. . » Тип функции может быть установлен как параметр или тип возвращаемого значения функции.
Типы функций могут быть присвоены любой переменной следующим образом:
var mathFunction: (Int, Int) -> Int = add
Функции являются частными случаями замыканий.Замыкания могут иметь одну из трех форм:
- Глобальные функции: они имеют имя и не могут фиксировать значение.
- Вложенные функции: у них есть имя, и они могут захватывать значения из своей включающей функции.
- Замыкающие выражения: они не имеют имени и могут захватывать значения из окружающего их контекста.
Замыкание можно создать, поместив тип функции в фигурные скобки и в ключевое слово
после типа возвращаемого значения.
Аргументы закрытия могут ссылаться на позицию i.е. $ 0
, $ 1
, $ 2
, $ 3
и так далее.
Закрытие с одним выражением может неявно возвращать результат своего единственного выражения, опуская ключевое слово return
из своего объявления.
Для замыкания многострочного выражения ключевое слово return
не может быть пропущено.
Если вам нужно передать закрывающее выражение функции, поскольку последний аргумент функции и закрывающее выражение слишком длинное, его можно записать как замыкающее замыкание.Завершающее замыкание записывается после круглых скобок () при вызове функции, даже если оно по-прежнему является аргументом функции. Когда вы используете синтаксис завершающего закрытия, вы не пишете метку аргумента для закрытия как часть вызова функции.
Замыкание как аргумент для метода callTrailing Closure (т.
Если закрытие является последним параметром метода, тогда swift позволяет писать так 🖕
Пример конечного замыкания с использованием reduce () Использование синтаксиса замыкающего замыкания аккуратно инкапсулирует функциональность замыкания сразу после функции, поддерживаемой замыканием, без необходимости заключать все замыкание во внешние скобки метода reduce (_ :)
.
Замыкание может захватить констант и переменных из окружающего контекста, в котором оно определено. Затем замыкание может ссылаться на и изменять значения этих констант и переменных из своего тела, даже если исходная область видимости, которая определяла константы и переменные, больше не существует.
В Swift простейшей формой замыкания, которое может захватывать значения, является вложенная функция, записанная в теле другой функции. Вложенная функция может захватывать любые аргументы своей внешней функции, а также может захватывать любые константы и переменные, определенные во внешней функции.
Эта функция makeIncrementer
принимает один аргумент, то есть Int, в качестве входных данных и возвращает тип функции, то есть () -> Int
. Это означает, что он возвращает функцию , а не простое значение. Возвращаемая им функция не имеет параметров и возвращает значение Int
каждый раз при вызове.
Здесь сумма
— аргумент, runningTotal
объявлен как переменная и инициализируется 0.Вложенная функция incrementer
захватывает суммы
и runningTotal
из окружающего контекста.
Давайте посмотрим makeIncrementer
в действии:
Примечание. В качестве оптимизации Swift может вместо этого захватить и сохранить копию значения, если это значение не изменяется при закрытии, и если значение не изменяется после закрытие создается.
Swift также обрабатывает все операции управления памятью, связанные с удалением переменных, когда они больше не нужны.
Чтобы избавиться от длинного закрывающего выражения в аргументе функции, вы можете использовать typealias.
Параметры закрытия по умолчанию экранировались до Swift 3. Замыкание не выходило за рамки тела функции, если параметры закрытия были помечены как неоткрывающиеся.
В Swift 3 это было отменено. Когда вы передаете закрытие в качестве аргумента функции, закрытие выполняется с телом функции и возвращает компилятор обратно. По завершении выполнения переданное закрытие выходит за пределы области видимости и больше не существует в памяти.
Параметры закрытия — без экранирования по умолчанию, если вы хотите избежать выполнения закрытия, вы должны использовать @escaping с параметрами закрытия.
Жизненный цикл неэкранирующего закрытия:
1. Передайте закрытие как аргумент функции во время вызова функции.
2. Поработайте в функции и затем выполните закрытие.
3. Функция возвращается.
Из-за лучшего управления памятью и оптимизации Swift изменил все замыкания, чтобы по умолчанию не было экранирования. CaptureList.swift
— это пример закрытия без экранирования .
Примечание: @ аннотация без экранирования применяется только к типам функций
О закрытии говорят escape функции, когда закрытие передается в качестве аргумента функции, но вызывается после возврата из функции. Пометка закрытия с помощью @escaping
означает, что вы должны явно ссылаться на self
внутри закрытия.
Жизненный цикл закрытия @escaping:
1.Передайте закрытие как аргумент функции во время вызова функции.
2. Проделайте некоторые дополнительные работы в функции.
3. Функция выполняет закрытие асинхронно или сохраняется.
4. Функция возвращается.
Давайте посмотрим, где замыкания экранируются по умолчанию:
- Переменные типа функции неявно экранируются
- typealiases неявно экранируются
- Необязательные замыкания являются неявным экранированием
02 Общая ошибка:

- Отметить закрытие как , экранирование
- Или оставьте поведение @noescape по умолчанию, сделав закрытие необязательным
Атрибут Swift @autoclosure
позволяет определить аргумент, который автоматически получает завернутый в закрытие. Он не принимает никаких аргументов и при вызове возвращает значение выражения, заключенного в него. Это удобство синтаксиса позволяет вам опускать фигурные скобки вокруг параметра функции, записывая обычное выражение вместо явного закрытия.
Например, функция assert (condition: message: file: line :)
принимает автоматическое закрытие для своих параметров condition
и message
; его condition
параметр оценивается только в отладочных сборках, а его message
параметр оценивается только в том случае, если condition
равно false
.
func assert (_ выражение: @autoclosure () -> Bool,
_ message: @autoclosure () -> String) {}
Чтобы использовать @autoclosure
с @escaping
, синтаксис атрибута:
@autoclosure @escaping () -> Bool
«Замыкания Swift и блоки Objective-C совместимы, поэтому вы можете передавать закрытия Swift методам Objective-C, ожидающим блоков. Замыкания и функции Swift имеют один и тот же тип, поэтому вы даже можете передать имя функции Swift. Замыкания имеют аналогичную семантику захвата, что и блоки, но отличаются одним ключевым моментом: переменные изменяются, а не копируются. Другими словами, поведение __block в Objective-C является поведением по умолчанию для переменных в Swift ».
Решение зависит от проблемы. Более того, Apple переключает внимание на шаблон обратного вызова. UIAlertAction
является примером этого.
https: // medium.com / @ abhimuralidharan / function-swift-all-about-closures-310bc8af31dd
https://medium.com/@kumarpramod017/what-do-mean-escaping-and-nonescaping-closures-in-swift-d404d721f392d
https://oleb.net/blog/2016/10/optional-non-escaping-closures/https://swiftunboxed.com/lang/closures-escaping-noescape-swift3/
https: // medium. com / @ johnsundell / using-autoclosure-when-designing-swift-apis-67fe20a8b2e
Итерация коллекциис замыканиями | Raywenderlich.

Ранее вы узнали о функциях. Но у Swift есть еще один объект, который вы можете использовать для разбиения кода на многократно используемые части: закрытие . Они становятся особенно полезными при работе с коллекциями.
Замыкание — это просто функция без имени; вы можете присвоить его переменной и передавать как любое другое значение. В этой главе показано, насколько удобными и полезными могут быть закрытия.
Основы закрытия
Замыкания названы так потому, что они могут «закрывать» переменные и константы в пределах собственной области видимости замыкания. Это просто означает, что замыкание может получить доступ к значениям любой переменной или константы из окружающего контекста. Говорят, что переменные и константы, используемые в теле укупорочного средства, были захвачены укупорочным средством .
Вы можете спросить: «Если замыкания — это функции без имен, то как их использовать?» Чтобы использовать замыкание, вы сначала должны присвоить его переменной или константе.
Вот объявление переменной, которая может содержать закрытие:
var multiplyClosure: (Int, Int) -> Int
multiplyClosure
принимает два значения Int
и возвращает Int
. Обратите внимание, что это в точности то же самое, что и объявление переменной для функции. Как я уже сказал, замыкание — это просто функция без имени. Тип закрытия — это тип функции.
Чтобы объявление скомпилировалось на игровой площадке, вам необходимо предоставить начальное определение, например:
var multiplyClosure = {(a: Int, b: Int) -> Int в
вернуть а * б
}
Определив закрывающую переменную, вы можете использовать ее, как если бы она была функцией, например:
пусть результат = multiplyClosure (4, 2)
Как и следовало ожидать, результат
равен 8. Однако опять же есть небольшая разница.
Обратите внимание, что у замыкания нет внешних имен для параметров. Их нельзя настроить так, как с помощью функций.
Сокращенный синтаксис
По сравнению с функциями, укупорочные средства должны быть легкими. Есть много способов сократить их синтаксис. Во-первых, как и в случае с обычными функциями, если замыкание состоит из одного оператора return, вы можете опустить ключевое слово
return
, например:
multiplyClosure = {(a: Int, b: Int) -> Int в
а * б
}
multiplyClosure = {(a, b) в
а * б
}
multiplyClosure = {
0 долларов США * 1 доллар США
}
funcperateOnNumbers (_ a: Int, _ b: Int,
операция: (Int, Int) -> Int) -> Int {
пусть результат = операция (a, b)
печать (результат)
вернуть результат
}
пусть addClosure = {(a: Int, b: Int) в
а + б
}
operationOnNumbers (4, 2, операция: addClosure)
func addFunction (_ a: Int, _ b: Int) -> Int {
а + б
}
operationOnNumbers (4, 2, операция: addFunction)
operationOnNumbers (4, 2, операция: {(a: Int, b: Int) -> Int в
вернуть a + b
})
operationOnNumbers (4, 2, операция: {$ 0 + $ 1})
operationOnNumbers (4, 2, операция: +)
operationOnNumbers (4, 2) {
$ 0 + $ 1
}
Синтаксис множественных завершающих замыканий
Если функция имеет несколько закрытий для входов, вы можете вызвать ее специальным сокращенным способом. Предположим, у вас есть такая функция:
последовательность функций (первая: () -> Void, вторая: () -> Void) {
первый()
второй()
}
секвенированный {
print ("Привет,", терминатор: "")
} второй: {
print ("мир.")
}
Замыкания без возврата значения
До сих пор все замыкания, которые вы видели, принимали один или несколько параметров и возвращали значения. Но, как и функции, для этого не требуется закрытие.Вот как вы объявляете замыкание, которое не принимает параметров и ничего не возвращает:
let voidClosure: () -> Void = {
print («Swift Apprentice - это круто!»)
}
voidClosure ()
Sse otcxd zutevwsulid huyihe kwosa emo mi yuluqotezy. Mie yojc xurpuko i vapiql fzpi, gi Xyiyj pfoyz yuo’ge qumjuzitq o zzejode. Vkuk ew nsuzo Joac
yorod od yazvv, etl id xiebn ililqsl tyis ips jiqu mohtahvg: mbo pjicota caperqm pegxukc.Захват из прицела
Наконец, давайте вернемся к определяющей характеристике замыкания: оно может обращаться к переменным и константам из своей собственной области видимости.
var counter = 0
let incrementCounter = {
счетчик + = 1
}
инкрементCounter ()
incrementCounter ()
incrementCounter ()
incrementCounter ()
incrementCounter ()
func countingClosure () -> () -> Int {
var counter = 0
пусть incrementCounter: () -> Int = {
счетчик + = 1
счетчик возврата
}
return incrementCounter
}
пусть counter1 = countingClosure ()
пусть counter2 = countingClosure ()
counter1 () // 1
counter2 () // 1
counter1 () // 2
counter1 () // 3
counter2 () // 2
Сортировка по индивидуальному заказу с крышками
Застежки пригодятся, когда вы начнете более детально изучать коллекции. В главе 7 вы использовали метод массива
sort
для сортировки массива. Указав закрытие, вы можете настроить порядок сортировки. Вы вызываете sorted ()
, чтобы получить отсортированную версию массива следующим образом:
let names = ["ZZZZZZ", "BB", "A", "CCCC", "EEEEE"]
names.sorted ()
// [«A», «BB», «CCCC», «EEEEE», «ZZZZZZ»]
names.sorted {
0 долларов США> 1 доллар США
}
// ["ZZZZZZ", "EEEEE", "CCCC", "BB", "A"]
Перебор коллекций с закрытием
В Swift коллекции реализуют некоторые очень удобные функции, часто связанные с функциональным программированием . Эти функции представлены в виде функций, которые можно применить к коллекции для выполнения над ней операций.
let values = [1, 2, 3, 4, 5, 6]
values.forEach {
print ("\ ($ 0): \ ($ 0 * $ 0)")
}
цены var = [1.
5, 10, 4.99, 2.30, 8.19]
let largePrices = price.filter {
$ 0> 5
}
func filter (_ isIncluded: (Element) -> Bool) -> [Element]
let largePrice = price.first {
$ 0> 5
}
let salePrices = sizes.map {
0 долларов США * 0,9
}
let userInput = ["0", "11", "ха-ха", "42"]
let numbers1 = userInput.map {
Int (0 долл. США)
}
пусть числа2 = userInput.compactMap {
Int (0 долл. США)
}
let userInputNested = [["0", "1"], ["a", "b", "c"], ["🐕"]]
let allUserInput = userInputNested.flatMap {
$ 0
}
let sum = price.reduce (0) {
$ 0 + $ 1
}
Ajogisu mui lohrayoml rgo yvasb ip guaf nwut ql a fevfoozahp cogwelf rce tmifi xi pme wagxev ur ipovv ac jkud pdoqu.Wiu douxz azu bkit je dojvunoje yxa xupet pimie ah почва bguws lofa go:
пусть сток = [1.5: 5, 10: 2, 4.99: 20, 2.30: 5, 8.19: 30]
let stockSum = stock.reduce (0) {
$ 0 + $ 1, клавиша * Double (значение $ 1.)
}
let farmAnimals = ["🐎": 5, "🐄": 10, "🐑": 50, "🐶": 1]
let allAnimals = farmAnimals.reduce (into: []) {
(результат, это: (ключ: String, значение: Int)) в
для _ в 0 ..
пусть removeFirst = price.dropFirst ()
пусть removeFirstTwo = price.dropFirst (2)
removeFirst = [10, 4.99, 2.30, 8.19]
removeFirstTwo = [4.99, 2.30, 8.19]
пусть removeLast = price.dropLast ()
пусть removeLastTwo = price.dropLast (2)
removeLast = [1.
5, 10, 4.99, 2.30]
removeLastTwo = [1,5, 10, 4,99]
let firstTwo = price.prefix (2)
пусть lastTwo = price.suffix (2)
firstTwo = [1.
5, 10]
lastTwo = [2.30, 8.19]
price.removeAll () {$ 0> 2} // цены теперь [1,5]
price.removeAll () // цены теперь пустой массив
Ленивые коллекции
Иногда у вас может быть огромная или даже бесконечная коллекция, но вы хотите каким-то образом получить к ней доступ. Конкретным примером этого могут быть все простые числа.Это бесконечный набор чисел. Итак, как вы можете работать с этим набором? Войдите в ленивую коллекцию .
func isPrime (_ number: Int) -> Bool {
if number == 1 {return false}
если число == 2 || число == 3 {вернуть истину}
for i in 2 ... Int (Double (number) .squareRoot ()) {
если число% i == 0 {return false}
}
вернуть истину
}
var primes: [Int] = []
var i = 1
а простые числа.count <10 {
if isPrime (i) {
primes.append (i)
}
я + = 1
}
primes.forEach {print ($ 0)}
пусть простые числа = (1 ...). Ленивый
.filter {isPrime ($ 0)}
.prefix (10)
primes.forEach {print ($ 0)}
Мини-упражнения
- Создайте постоянный массив с именем
names
, который содержит некоторые имена в виде строк.Подойдут любые имена - убедитесь, что их больше трех. Теперь используйтеreduce
, чтобы создать строку, которая представляет собой объединение каждого имени в массиве. - Используя тот же массив
names
, сначала отфильтруйте массив, чтобы он содержал только имена, длина которых превышает четыре символа, а затем создайте такое же соединение имен, как в приведенном выше упражнении. (Подсказка: вы можете связать эти операции вместе.) - Создайте словарь констант с именем
namesAndAges
, который содержит некоторые имена в виде строк, сопоставленных возрасту в виде целых чисел.Теперь используйтефильтр
, чтобы создать словарь, содержащий только людей младше 18 лет. - Используя тот же словарь
namesAndAges
, отфильтруйте взрослых (от 18 лет и старше), а затем используйте карту
Вызовы
Прежде чем двигаться дальше, рассмотрим несколько задач, позволяющих проверить свои знания об итерациях коллекции с замыканиями. Лучше всего попытаться решить их самостоятельно, но есть решения, если вы застряли.Они поставляются вместе с загрузкой или доступны по ссылке на исходный код печатной книги, указанной во введении.
Вызов 1: Повторение
Ваша первая задача - написать функцию, которая будет запускать заданное закрытие заданное количество раз.
func repeatTask (раз: Int, задача: () -> Void)
Задача 2: Суммы закрытия
В этом задании вы собираетесь написать функцию, которую можно будет повторно использовать для создания различных математических сумм.
func mathSum (длина: Int, серия: (Int) -> Int) -> Int
Задача 3: Функциональные рейтинги
В этом последнем задании у вас будет список названий приложений с присвоенными им оценками. Обратите внимание - это все вымышленные приложения! Создайте словарь данных следующим образом:
пусть appRatings = [
«Календарь Pro»: [1, 5, 5, 4, 2, 1, 5, 4],
«Посланник»: [5, 4, 2, 5, 4, 1, 1, 2],
«Общайся»: [2, 1, 2, 2, 1, 2, 4, 2]
]
Ключевые моменты
- Замыкания - это функции без имен. Их можно присвоить переменным и передать как параметры функциям.
- Замыкания имеют сокращенный синтаксис , что делает их намного проще в использовании, чем другие функции.
- Замыкание может захватить переменных и констант из окружающего контекста.
- Замыкание может использоваться для управления сортировкой коллекции.
- Для коллекций существует удобный набор функций, которые можно использовать для перебора коллекции и ее преобразования. Преобразования включают отображение каждого элемента в новое значение, фильтрацию определенных значений и сокращение коллекции до одного значения.
- Ленивые коллекции можно использовать для оценки коллекции только в случае крайней необходимости, что означает, что вы можете легко работать с большими, дорогими или потенциально бесконечными коллекциями.