Видеокурсы по изучению языка программирования Swift. Подробнее

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

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

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

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

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

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

Возвращаемый тип makeIncrementer Void -> Int. Это значит, что он возвращает функцию, а не простое значение. Функцию, которую она возвращает не имеет параметров и возвращает Int каждый раз как ее вызывают. Узнать как функции могут возвращать другие функции можно в главе "Функциональные типы".

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

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

Функция makeIncrementer объявляет вложенную функцию incrementer, которая непосредственно и занимается увеличением значения. Эта функция просто добавляет amount к runningTotal и возвращает результат.

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

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

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

Так как она не изменяет amount, функция incrementer захватывает и хранит копию значения, хранимого в amount. Это значение хранится вместе с новой функцией incrementor.

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

Заметка

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

Приведем пример makeIncrementor в действии:

let incrementByTen = makeIncrementer(forIncrement: 10)

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

incrementByTen()
// возвращает 10
incrementByTen()
// возвращает 20
incrementByTen()
// возвращает 30

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

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
//возвращает значение 7

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

incrementByTen()
//возвращает 40

Заметка

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

Swift: 
3.0