Выполнение кода после вложенных обработчиков завершения - PullRequest
1 голос
/ 15 февраля 2020

Я пишу расширение приложения Safari и хочу извлечь URL для активной страницы в моем контроллере представления.

Это означает, что вложенные обработчики завершения выбирают окно, выбирают вкладку, выбирают страницу , чтобы получить доступ к его свойствам. Раздражает, но достаточно просто. Это выглядит так:

func doStuffWithURL() {

    var url: URL?

    SFSafariApplication.getActiveWindow { (window) in
        window?.getActiveTab { (tab) in
            tab?.getActivePage { (page) in
                page?.getPropertiesWithCompletionHandler { (properties) in
                    url = properties?.url
                }
            }
        }
    }

    // NOW DO STUFF WITH THE URL
    NSLog("The URL is \(String(describing: url))")

}

Очевидная проблема - это не работает. Будучи обработчиками завершения, они не будут выполняться до конца функции. Переменная url будет равна нулю, и все будет сделано до того, как будет предпринята любая попытка получить URL.

Одним из способов решения этой проблемы является использование DispatchQueue. Это работает, но код действительно ужасен:

func doStuffWithURL() {

    var url: URL?

    let group = DispatchGroup()
    group.enter()
    SFSafariApplication.getActiveWindow { (window) in
        if let window = window {
            group.enter()
            window.getActiveTab { (tab) in
                if let tab = tab {
                    group.enter()
                    tab.getActivePage { (page) in
                        if let page = page {
                            group.enter()
                            page.getPropertiesWithCompletionHandler { (properties) in
                                url = properties?.url
                                group.leave()
                            }
                        }
                        group.leave()
                    }
                }
                group.leave()
            }
        }
        group.leave()
    }

    // NOW DO STUFF WITH THE URL
    group.notify(queue: .main) {
        NSLog("The URL is \(String(describing: url))")
    }

}

Блоки if необходимы, чтобы знать, что мы не имеем дело с нулевым значением. Мы должны быть уверены, что обработчик завершения вернется, и поэтому .leave() вызов, прежде чем мы сможем вызвать .enter(), чтобы вернуться к нулю.

Я даже не могу похоронить все это уродство в некотором роде getURLForPage() функция или расширение (добавление какого-либо вида SFSafariApplication.getPageProperties было бы моим предпочтением), поскольку очевидно, что вы не можете вернуться из функции из блока .notify.

Хотя я пытался создать функцию с использованием queue.wait и другой DispatchQueue, как описано в следующем ответе, чтобы иметь возможность использовать return…

{ ссылка }

… для меня неудивительно, что это вызывает тупик , поскольку .wait все еще выполняется в главной очереди.

Есть ли лучший способ добиться этого? Кстати, «делом» является обновление пользовательского интерфейса по запросу пользователя, поэтому он должен находиться в главной очереди.

Редактировать: Во избежание сомнений, это не вопрос iOS. Хотя применяются аналогичные принципы, расширения приложений Safari являются функцией Safari только для macOS.

1 Ответ

0 голосов
/ 16 февраля 2020

Благодаря предложениям Ларме в комментариях я придумала решение, которое скрывает уродство, пригодно для повторного использования и сохраняет код чистым и стандартным.

Вложенные обработчики завершения могут быть заменены расширением к классу SFSafariApplication, так что в основной части кода требуется только один.

extension SFSafariApplication {

    static func getActivePageProperties(_ completionHandler: @escaping (SFSafariPageProperties?) -> Void) {

        self.getActiveWindow { (window) in
            guard let window = window else { return completionHandler(nil) }
            window.getActiveTab { (tab) in
                guard let tab = tab else { return completionHandler(nil) }
                tab.getActivePage { (page) in
                    guard let page = page else { return completionHandler(nil) }
                    page.getPropertiesWithCompletionHandler { (properties) in
                        return completionHandler(properties)
                    }
                }
            }
         }

    }

}

Тогда в коде его можно использовать как:

func doStuffWithURL() {

    SFSafariApplication.getActivePageProperties { (properties) in
        if let url = properties?.url {
            // NOW DO STUFF WITH THE URL
            NSLog("URL is \(url))")
        } else {
            // NOW DO STUFF WHERE THERE IS NO URL
            NSLog("URL ERROR")
        }
    }

}
...