Основы Swift / 9.2. Захват величин


Видео


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

В Swift простейшая форма замыкания, которое может захватывать значения - это вложенная функция, написанная внутри тела другой функции. Вложенная функция может захватывать любой из аргументов внешней функции, а так же любые константы и переменные, определённые во внешней функции.
@{9.2\1\1}
func makeInrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
Здесь приведён пример функции с названием makeIncrementer, которая содержит вложенную функцию с названием incrementer. Вложенная функция incrementer() захватывает два значения, runningTotal и amount, из своего окружающего контекста. После захвата этих значений incrementer возвращается makeIncrementer в качестве замыкания, которое увеличивает runningTotal на значение amount всякий раз при своём вызове.

Возвращаемый тип у makeInсrementer () -> Int. Это означает, что он возвращает функцию, а не простое значение. Функция, им возвращаемая, не имеет параметров, а возвращаемое значение всегда имеет при вызове тип Int.

Функция makeInсrementer(forIncrement:) определяет целое число-переменную с названием runningTotal для хранения текущего значения инкрементера. Инициализируется она значением 0.

Функция makeInсrementer(forIncrement:) имеет единственный параметр типа Int с ярлыком аргумента forIncrement и именем параметра amount. Значение аргумента передаётся в этот параметр для определения того, на сколько должно возрасти значение runningTotal всякий раз при вызове возвращаемой функции-инкрементера. Функция makeInсrementer определяет вложенную функцию с названием incrementer, которая уже осуществляет увеличение. Функция просто прибавляет amount к runningTotal и возвращает результат.
@{9.2\2}
func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}
При отдельном рассмотрении вложенная функция incrementer() может выглядеть необычной.

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

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

Swift берёт на себя полностью управление памятью по высвобождению переменных, когда они более не нужны.
@{9.2\1\2}
let incremenetByTen = makeInrementer(forIncrement: 10)
Этот пример устанавливает значение константы под названием incrementByTen для ссылки на функцию-инкрементер, которая прибавляет 10 к своей переменной runningTotal всякий раз при вызове функции.
@{9.2\1\3}
incremenetByTen()
// возвращает значение 10
incremenetByTen()
// возвращает значение 20
incremenetByTen()
// возвращает значение 30
Вызов функции несколько раз покажет её поведение в действии.
@{9.2\1\4}
Если Вы создадите второй инкрементер, он будет хранить своё собственное значение переменной runningTotal.
let incrementBySeven = makeInrementer(forIncrement: 7)
incrementBySeven()
// возвращает значение 7
@{9.2\1\5}
Вызов оригинального инкрементера (incrementByTen) вновь продолжит увеличивать свою собственную переменную runningTotal и не окажет эффекта на переменную, захваченную incrementBySeven.
incremenetByTen()
// возвращает значение 40
Замыкания как ссылочные типы
В примере выше incrementBySeven и incrementByTen являются константами, но замыкания, на которые ссылаются эти константы, всё ещё могут инкрементировать свои переменные runningTotal, которые они захватили. Так происходит ввиду того, функции и замыкания - это ссылочные типы.

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

Это так же означает, что если Вы присвоите замыкание двум разным константам или переменным, обе эти константы или переменные будут ссылаться на одно и то же замыкание:
@{9.2\1\6}
let alsoIncrementByTen = incremenetByTen
alsoIncrementByTen()
// возвращает значение 50