Swift @escaping та завершення обробки


98

Я намагаюся зрозуміти «закриття» Свіфта точніше.

Але @escapingі Completion Handlerїх занадто важко зрозуміти

Я шукав багато поштових повідомлень Свіфта та офіційні документи, але відчув, що цього ще недостатньо.

Це приклад коду офіційних документів

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Я чув, що існують два способи та причини @escaping

Перший - для зберігання блокування, другий - для операційних цілей Async.

Наступні мої запитання :

По-перше, якщо doSomethingExecutes, то someFunctionWithEscapingClosureбуде виконано параметр закриття, і закриття буде збережено у глобальному масиві змінних.

Я думаю, що закриття - {self.x = 100}

Як selfу {self.x = 100}, що зберігається в глобальній змінній, completionHandlersможна підключитися до instanceцього об’єкта SomeClass?

По-друге, я так розумію someFunctionWithEscapingClosure.

Для зберігання локального закриття змінної completionHandlerдо глобальної змінної we usingключового слова "завершенняHandlers @ escape"!

без повернення @escapingключового слова someFunctionWithEscapingClosureлокальна змінна completionHandlerбуде видалена з пам'яті

@escaping це зберегти це закриття в пам'яті

Чи це правильно?

Нарешті, мені просто цікаво існування цієї граматики.

Можливо, це дуже елементарне питання.

Якщо ми хочемо, щоб якась функція виконувалася після якоїсь конкретної функції. Чому ми не викликаємо якусь функцію після виклику певної функції?

Які відмінності між використанням вищезазначеного шаблону та використанням функцією зворотного виклику, що виходить?

Відповіді:


122

Швидкий обробник робіт із вдосконалення та втечі:

Як пояснює Боб Лі у своєму дописі до блогу Завершення обробників у Swift with Bob :

Припустимо, користувач оновлює додаток під час його використання. Ви обов'язково хочете повідомити користувача про його завершення. Можливо, ви захочете спливати вікно, на якому написано: "Вітаю, зараз ви можете сподобатися!"

Отже, як запустити блок коду лише після завершення завантаження? Далі, як ви анімуєте певні об'єкти лише після того, як контролер перегляду перейшов до наступного? Що ж, ми дізнаємось, як створити такого, як бос.

На основі мого розширеного списку словникових запасів стоять обробники доробки

Робіть речі, коли все зроблено

Пост Боба надає ясність щодо обробників завершень (з точки зору розробника він точно визначає, що ми повинні розуміти).

@ закриття показів:

Коли хтось передає закриття в аргументах функції, використовуючи його після того, як тіло функції виконується, і повертає компілятор назад. Коли функція закінчується, область пройденого закриття існує і існує в пам'яті, поки закриття не виконується.

Існує кілька способів уникнути закриття функції, що містить:

  • Зберігання: Коли вам потрібно зберегти закриття в глобальній змінній, властивість або будь-який інший накопичувач, який існує в минулому пам'яті функції виклику, виконайте виконання і поверніть компілятор назад.

  • Асинхронне виконання: Коли ви виконуєте закриття асинхронно на черзі відправлення, черга буде зберігати закриття в пам'яті для вас, і вона може бути використана в майбутньому. У цьому випадку ви не маєте поняття, коли закриття буде виконано.

Коли ви намагаєтесь використовувати закриття в цих сценаріях, компілятор Swift покаже помилку:

скріншот помилок

Щоб отримати більш чітку інформацію про цю тему, ви можете ознайомитись з цією публікацією на "Середній" .

Додаємо ще один бал, який повинен розуміти кожен розробник ios:

  1. Закриття закриття : закривається закриття - це закриття, яке викликається після того, як функція, яку він передав, повертається. Іншими словами, він переживає функцію, якій він був переданий.
  2. Незакриття закриття : Закриття, яке викликається в межах функції, в яку він був переданий, тобто до повернення.

@shabhakar, що робити, якщо ми зберігаємо закриття, але не називаємо його пізніше. Або якщо метод викликали двічі, але ми викликали закриття лише один раз. Оскільки ми знаємо, що результат однаковий.
user1101733

@ user1101733 Я думаю, що ви говорите про уникнення закриття, закриття не виконається, поки ви не зателефонуєте. У наведеному вище прикладі, якщо виклик doSomething метод 2 рази 2 завершенняHandler об'єкт додасть у завершенняHandlers масив. Якщо ви берете перший об’єкт із завершення масиву Handand і зателефонуєте, він виконається, але кількість масивів завершенняHandlers залишиться незмінною (2).
Діпак

@Deepak, так про закриття втечі. припустимо, ми не використовуємо масив і використовуємо звичайну змінну для зберігання довідки про закриття, оскільки ми виконуємо останній виклик. Чи буде якась пам'ять, зайнята попередніми закриттями, які ніколи не зателефонують?
користувач1101733

1
@ user1101733 Закриття є типовим типом (як клас), коли ви присвоюєте нове закриття змінній, тоді властивість / змінна вказуватиме на нове закриття, тому ARC буде розміщувати пам'ять для попередніх закриттів.
Діпак

28

Ось невеликий клас прикладів, які я використовую, щоб нагадати собі, як працює @escaping.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
Цей код невірний. Тут відсутня @escapingкваліфікація.
Роб

Мені це найбільше сподобалосьi.e. escape the scope of this function.
Гал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.