Основы Swift / 11.5. Свойства - Замыкания


Видео


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

Один из способов того, что замыкание может убежать, это быть хранимым в переменной, определённой вне функции. В качестве примера, многие функции, которые начинают асинхронные операции, принимают аргумент-замыкание в качестве обработчика завершения. Функция возвращает управление после начала операции, но замыкание не будет вызвано, пока операция не завершится - то есть замыканию нужно сбежать, чтобы быть вызванным позже. Например:
@{11.5\1\1}
var completeionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(_ completionHandler: @escaping () -> Void) {
    completeionHandlers.append(completionHandler)
}

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

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

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Выведет "200"

completeionHandlers.first?()
print(instance.x)
// Выведет "100"
Функция someFunctionWithEscapingClosure(_:) принимает замыкание в качестве своего аргумента и добавляет его в массив, определённый вне функции. Если бы Вы не пометили этот параметр функции с помощью @escaping, Вы бы получили ошибку компиляции.

Пометка замыкания словом @escaping означает, что Вы должны ссылаться на self явно внутри замыкания. Например, в коде ниже замыкание, переданное в someFunctionWithEscapingClosure(_:) - это убегающее замыкание, что означает, что оно должно ссылаться на self явно. Напротив, замыкание переданное в someFunctionWithNonescapingClosure(_:) - это неубегающее замыкание, что значит, что оно может ссылаться на self неявно.
Автозамыкания
Автозамыкание - это замыкание, которое автоматически создаётся для оборачивания выражения, которое передаётся в качестве аргумента в функцию. Оно не принимает никаких аргументов, а будучи вызванным, возвращает значение выражения, заключенного внутри него. Этот синтаксический сахар позволяет Вам опустить скобки вокруг параметра функции, написав обычное выражение вместо явного замыкания.

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

Автозамыкание позволяет Вам отложить вычисление, так как код внутри не будет выполнен, пока Вы не вызовите замыкание. Отложенные вычисления полезны для кода с побочными эффектами или вычислительной дороговизной, так как они позволяют Вам управлять тем, когда код будет вычислен. Код ниже демонстрирует, как замыкание откладывает вычисление.
@{11.5\2\1}
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Выведет "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Выведет "5"

print("Now serving \(customerProvider())!")
// Печатает "Now serving Chris!"
print(customersInLine.count)
// Выведет "4"
И хотя первый элемент массива customersInLine удаляется внутри кода замыкания, элемент массива не будет удалён, пока замыкание не будет действительно вызвано. Если замыкание не будет вызвано никогда, то выражение внутри замыкания никогда не будет вычислено, что значит, что элемент массива никогда не будет убран. Заметьте, что тип customerProvider не String, но () -> String - функция без параметров, которая возвращает строку.

Вы получаете то же поведение отложенных вычислений, когда передаёте замыкание в качестве аргумента функции.
@{11.5\2\2}
// customersInLine теперь ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: { customersInLine.remove(at: 0) } )
// Печатает "Now serving Alex!"
Функция serve(customer:) в листинге выше принимает явно замыкание, которое возвращает имя клиента. Версия функции serve(customer:) ниже выполняет то же действие, но вместо принятия явного замыкания, она принимает автозамыкание, при этом её тип параметра помечается с помощью атрибута @autoclosure. Теперь Вы можете вызвать эту функцию так, как если бы она принимала String аргумент, а не замыкание. Аргумент автоматически преобразуется в замыкание, так как тип параметра customerProvider помечен атрибутом @autoclosure.
@{11.5\2\3}
// customersInLine теперь ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Выведет "Now serving Ewa!"
Переиспользование автозамыканий
Переиспользование автозамыканий может сделать Ваш код сложным для понимания. Контекст и имя функции могут помочь сделать более понятным, какие вычисления происходят.
Убегающее автозамыкание
Если Вы хотите создать автозамыкание, которому позволено убегать, используйте и атрибут @autoclosure, и @esaping.
@{11.5\2\4}
// customersInLine теперь ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Печатает "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Выведет "Now serving Barry!"
// Выведет "Now serving Daniella!"
Вместо вызова замыкания, передаваемого в качестве аргумента customerProvider, функция collectCustomerProviders(_:) дополняет замыкание в массив customerProviders. Этот массив определён вне контекста функции, что значит, что замыкания в массиве могут быть выполнены после возврата функции. В результате значение аргумента customerProvider должно иметь возможность сбежать из контекста функции.