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

Правильное размещение списка захвата во вложенных затворах в быстром

Где я могу определить захваченные ссылки для вложенных замыканий в Swift?

Возьмите этот код в качестве примера:

import Foundation

class ExampleDataSource {
    var content: Any?
    func loadContent() {
        ContentLoader.loadContentFromSource() { [weak self] loadedContent in
            // completion handler called on background thread
            dispatch_async(dispatch_get_main_queue()) { [weak self] in
                self?.content = loadedContent
            }
        }
    }
}

class ContentLoader {
    class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
        /*
        Load content from web asynchronously, 
        and call completion handler on background thread.
        */
    }
}

В этом примере [weak self] используется в обоих закрывающих закрытиях, однако компилятор совершенно счастлив, если я опускаю [weak self] из одного из закрывающих закрытий.

Итак, это оставляет мне 3 варианта определения моего списка захвата:

  • определять привязки на каждом вложенном замыкании, ведущем к ссылке
  • определять захваты только для первого закрытия.
  • Определяет привязки только к самому вложенному закрытию, которое фактически использует ссылку.

Мой вопрос:

Если я знаю, что мой ExampleDataSource может быть nil в какой-то момент, с чем лучше всего пойти?

4b9b3361

Ответ 1

Важно отметить, что GCD dispatch_async НЕ приведет к циклу сохранения. Другими словами, когда блок завершил выполнение, GCD не сохранит никаких ссылок, сделанных внутри блока.

То же самое не относится к сильным ссылкам между классами или сильным ссылкам в закрытии, назначенным для свойства экземпляра. Документация для Apple

В этом примере правильным ответом является вариант 2, чтобы определять захваты только при первом закрытии.

В целях тестирования я немного изменил код:

class ExampleDataSource {
    init() {
        print("init()")
    }
    deinit {
        print("deinit")
    }
    var content: Any?
    func loadContent() {
        print("loadContent()")
        ContentLoader.loadContentFromSource() { [weak self] loadedContent in
            dispatch_async(dispatch_get_main_queue()) {
                print("loadedContent")
                self?.content = loadedContent
            }
        }
    }
}

class ContentLoader {
    class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
            sleep(5)  // thread will hang for 5 seconds
            completion(loadedContent: "some data")
        }
    }
}

Сначала создаю, var myDataSource: ExampleDataSource? = ExampleDataSource().

Затем я запустил myDataSource.loadContent().

Прежде чем обработчик завершения получит шанс запустить, я установил myDataSource = nil, удалив все ссылки на него.

Консоль отладки указывает, что ссылка на self не была сохранена:

init()
loadContent()
deinit
loadedContent

Похоже, мы нашли наш ответ! Но только ради завершения, дайте возможность проверить альтернативы...

Если [weak self] вместо этого записывается только на внутреннем большинстве закрывающих закрытий, GCD сохранит ExampleDataSource, пока блок не завершит выполнение, что объясняет, почему отладка будет выглядеть следующим образом:

init()
loadContent()
loadedContent
deinit

То же самое произойдет, если список захвата не включен, и мы никогда не по желанию не разворачиваем self, хотя компилятор пытается предупредить вас!

Хотя технически некорректно включать [weak self] во все трейлинг-блокировки, это отвлекает от читаемости кода и не чувствует себя "Swift-like".