Подтвердить что ты не робот

Скрытие закрытий в Swift

Я новичок в Swift, и я читал руководство, когда сталкивался с закрытием. Я не получил описания руководства вообще. Может кто-нибудь, пожалуйста, объясните мне, что ускользает от закрытия в Swift простым языком.

4b9b3361

Ответ 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.

Ответ 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