在嵌套完成处理程序之后执行代码

问题描述 投票:0回答:2

我正在编写一个 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
将为 nil,并且这些内容将在尝试获取 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
块来知道我们处理的不是 nil 值。 我们需要确定完成处理程序会返回,因此需要先调用
.leave()
,然后才能调用
.enter()
最终回到零。

我什至无法将所有这些丑陋埋藏在某种

getURLForPage()
函数或扩展中(添加某种
SFSafariApplication.getPageProperties
将是我的偏好),因为显然你无法从
.notify
块内的函数返回。

虽然我尝试使用

queue.wait
和不同的
DispatchQueue
创建一个函数,如以下答案中所述,以便能够使用 return…

https://stackoverflow.com/a/42484670/2081620

…对我来说这并不奇怪,它会导致死锁,因为

.wait
仍在主队列上执行。

有没有更好的方法来实现这一目标? 顺便说一句,“要做的事情”是根据用户请求更新 UI,因此需要位于主队列中。

编辑:为避免疑问,这不是 iOS 问题。 虽然类似的原则适用,但 Safari 应用程序扩展只是适用于 macOS 的 Safari 的一项功能。

swift macos completionhandler safari-app-extension
2个回答
0
投票

感谢 Larme 在评论中的建议,我想出了一个隐藏丑陋、可重用、并保持代码干净和标准的解决方案。

嵌套的完成处理程序可以替换为

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")
        }
    }

}

0
投票

给您的一些想法 - 从

活动选项卡
获取host

的示例

V1:

class SafariExtensionViewController: SFSafariExtensionViewController {

    override func viewDidAppear() {
        super.viewDidAppear()
        SFSafariApplication.domain_getCurrent { host in
            DispatchQueue.main.async {
                print(host)
                // ANY OTHER CODE
            }
        }
    }

    static func domain_getCurrent(completionHandler: @escaping (String) -> Void) {
        SFSafariApplication.getActiveWindow { window in
            window?.getActiveTab(completionHandler: { tab in
                tab?.getActivePage(completionHandler: { page in
                    page?.getPropertiesWithCompletionHandler({ properties in
                        let url = properties?.url
                        let host = url?.host
                        completionHandler(
                            host!
                        )
                    })
                })
            })
        }
    }

}

V2:

class SafariExtensionViewController: SFSafariExtensionViewController {

    override func viewDidAppear() {
        Task {
           if let host = await SFSafariApplication.domain_getCurrent() {
                print(host)
                // ANY OTHER CODE
            }
        }
    }

    static func domain_getCurrent() async -> String? {
        let windows = await SFSafariApplication.activeWindow()
        let tab = await windows?.activeTab()
        let page = await tab?.activePage()
        let properties = await page?.properties()
        let url = properties?.url
        let host = url?.host
        return host
    }

}

V3:

class SafariExtensionViewController: SFSafariExtensionViewController {

    override func viewDidAppear() {
        super.viewDidAppear()
        SFSafariApplication.getActiveWindow { window in
            window?.getActiveTab(completionHandler: { tab in
                tab?.getActivePage(completionHandler: { page in
                    page?.getPropertiesWithCompletionHandler({ properties in
                        DispatchQueue.main.async {
                            print(properties?.url?.host)
                            // ANY OTHER CODE
                        }
                    })
                })
            })
        }
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.