Я новичок в Swift, и я читал руководство, когда сталкивался с закрытием. Я не получил описания руководства вообще. Может кто-нибудь, пожалуйста, объясните мне, что ускользает от закрытия в Swift простым языком.
Скрытие закрытий в Swift
Ответ 1
Рассмотрим этот класс:
class A {
var closure: (() -> Void)?
func someMethod(closure: () -> Void) {
self.closure = closure
}
}
someMethod
назначает замыкание, переданное в, в свойство в классе.
Теперь вот еще один класс:
class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
}
Если я вызываю anotherMethod
, замыкание { self.number = 10 }
будет сохранено в экземпляре A
. Поскольку self
фиксируется в замыкании, экземпляр A
также будет содержать сильную ссылку на него.
Это в основном пример скрытого закрытия!
Вы, наверное, задаетесь вопросом: "Что? Так откуда же ушло экранирование и до?"
Закрытие выходит из области действия метода в область действия класса. И это можно назвать позже, даже в другом потоке! Это может вызвать проблемы, если не обрабатываться должным образом.
Чтобы избежать случайного выхода из закрытий и возникновения циклов сохранения и других проблем, используйте атрибут @noescape
:
class A {
var closure: (() -> Void)?
func someMethod(@noescape closure: () -> Void) {
}
}
Теперь, если вы попытаетесь написать self.closure = closure
, он не компилируется!
Update:
В Swift 3 все параметры закрытия не могут быть удалены по умолчанию. Вы должны добавить атрибут @escaping
, чтобы сделать закрытие возможным для выхода из текущей области. Это добавляет намного больше безопасности в ваш код!
class A {
var closure: (() -> Void)?
func someMethod(closure: @escaping () -> Void) {
}
}
Ответ 2
Я делаю более простой способ.
Рассмотрим следующий пример:
func testFunctionWithNonescapingClosure(closure:() -> Void) {
closure()
}
Вышеприведенное является неэкранирующим замыканием, поскольку замыкание вызывается перед возвратом метода.
Рассмотрим тот же пример с асинхронной операцией:
func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
DispatchQueue.main.async {
closure()
}
}
В приведенном выше примере содержится закрывающее закрытие, потому что вызов закрытия может произойти после возвращения функции из-за асинхронной операции.
var completionHandlers: [() -> Void] = []
func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
completionHandlers.append(closure)
}
В приведенном выше случае вы можете легко понять, что замыкание выходит за пределы тело функции, поэтому оно должно быть закрывающимся закрытием.
Для оптимизации компилятора в Swift 3 были добавлены закрытие и неэкранирование. Вы можете искать преимущества закрытия nonescaping
.
Ответ 3
Я считаю, что этот сайт очень полезен в этом отношении Простое объяснение:
Если замыкание передается как аргумент функции и оно вызывается после того, как функция вернется, замыкание будет экранировано.
Подробнее по ссылке, которую я прошел выше!:)
Ответ 4
Swift 4.1
Из справочника по языку: атрибуты языка программирования Swift (Swift 4.1)
Apple объясняет, что атрибут escaping
ясно.
Примените этот атрибут к типу параметров в объявлении метода или функции, чтобы указать, что значение параметров может быть сохранено для последующего выполнения. Это означает, что значение может пережить время жизни вызова. Параметры типа функции с атрибутом escape-типа требуют явного использования self. для свойств или методов. Для примера того, как использовать escape-атрибут, смотрите Escaping Closures
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
Функция
someFunctionWithEscapingClosure(_:)
принимает в качестве аргумента замыкание и добавляет его в массив, объявленный вне функции. Если вы не@escaping
параметр этой функции@escaping
, вы получите ошибку во время компиляции.Говорят, что замыкание экранирует функцию, когда замыкание передается в качестве аргумента функции, но вызывается после ее возврата. Когда вы объявляете функцию, которая принимает замыкание в качестве одного из своих параметров, вы можете написать @escaping перед типом параметров, чтобы указать, что закрытию разрешено экранировать.
Ответ 5
По умолчанию замыкания не экранируют. Для простого понимания вы можете рассматривать non_escaping замыкания как локальные замыкания (точно так же, как локальные переменные) и экранирование как глобальные замыкания (точно так же, как глобальные переменные). Это означает, что как только мы выйдем из тела метода, область действия non_escaping будет потеряна. Но в случае избежания закрытия память сохраняла закрытие в памяти.
*** Просто мы используем escape-замыкание, когда вызываем замыкание после любой асинхронной задачи внутри метода.
Non_escaping закрытие: -
func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
DispatchQueue.global(qos: .background).async {
print("Background")
completion(num1 + num2) // Error: Closure use of non-escaping parameter 'completion' may allow it to escape
}
return num1
}
override func viewDidLoad() {
super.viewDidLoad()
let ans = add(num1: 12, num2: 22, completion: { (number) in
print("Inside Closure")
print(number)
})
print("Ans = \(ans)")
initialSetup()
}
Поскольку это закрытие non_escaping, его область будет потеряна, как только мы выйдем из метода add. завершение (num1 + num2) никогда не вызовет.
Выход из закрытия: -
func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
DispatchQueue.global(qos: .background).async {
print("Background")
completion(num1 + num2)
}
return num1
}
Даже если метод return (т.е. мы выйдем из области видимости метода), будет вызвано замыкание. enter code here