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

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

var customersInLine = ["Крис", "Алеша", "Евгения", "Борька", "Данилка"]
let nextCustomer = { customersInLine.removeAtIndex(0) }
print(customersInLine.count)
// выводит "5"

print("Следующий клиент - \(nextCustomer())!")
// выводит “Следующий клиент - Крис!"
print(customersInLine.count)
// выводит “4”

Даже если первый элемент массива customersInLine удален в качестве части замыкания, то дальнейшее исполнение не поддерживается до тех пор, пока само замыкание фактически не будет вызвано. Если замыкание так и не вызывается, то выражение внутри него никогда и не вычисляется. Обратите внимание, что nextCustomer является не String, а () -> String, то есть функция не принимает аргументов, но возвращает строку. Вы получите то же самое поведение, когда сделаете это внутри функции:

// customersInLine is ["Алеша", "Евгения", "Борька", "Данилка"]
func serveNextCustomer(customer: () -> String) {
  print("Now serving \(customer())!")
}
serveNextCustomer( { customersInLine.removeAtIndex(0) } )
// выведет "Следующий клиент - Алеша!”

Функция serveNextCustomer(_:) описанная выше принимает явное замыкание, которое возвращает имя следующего клиента. Следующая версия этой же функции ниже выполняет ту же самую операцию, но вместо использования явного замыкания, она использует автозамыкание, поставив маркировку при помощи атрибута @autoclosure. Теперь вы можете вызывать функцию, как будто бы она принимает аргумент String вместо замыкания.

// customersInLine is ["Евгения", "Борька", "Данилка"]
func serveNextCustomer(@autoclosure customer: () ->  String) {
  print("Now serving \(customer())!")
}
serveNextCustomer(customersInLine.removeAtIndex(0))
// выведет "Следующий клиент - Евгения!"

Заметка

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

Атрибут @autoclosure предполагает наличие атрибута @noescape, который указывает на то, что замыкание используется только внутри функции. Что значит, что замыканию не позволяется храниться так, чтобы она могла “сбежать” из области видимости функции и исполниться после того, как функция уже возвратила значение. Если вы хотите, чтобы замыкание могло “сбежать”, то используйте другую форму атрибута - @autoclosure(escaping):

// customersInLine is ["Борька", "Данилка"]

var customerClosures: [() -> String] = []
func collectCustomerClosures(@autoclosure(escaping) customer: () -> String ) {
  customerClosures.append(customer)
}
collectCustomerClosures(customersInLine.removeAtIndex(0))
collectCustomerClosures(customersInLine.removeAtIndex(0))

print("Всего \(customerClosures.count) замыкани(я/й).")
// выведет "Collected 2 closures."
for customerClosure in customerClosures {
  print("Следующий клиент \(customerClosure())!")
}
// выведет "Следующий клиент - Борька!"
// выведет "Следующий клиент - Данилка!"

В коде выше, вместо того, чтобы вызывать переданное замыкание в качестве аргумента customer, функция collectCustomerClosures(_:) добавляет замыкание к массиву customerClosures. Массив объявлен за пределами функции, что означает, что замыкание в массиве может быть исполнено после того, как функция вернет значение. В результате значение аргумента customer должен иметь “разрешение” на “побег” из зоны видимости функции.