我正在编写一个 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 的一项功能。
感谢 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")
}
}
}
给您的一些想法 - 从
活动选项卡获取
host
的示例
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!
)
})
})
})
}
}
}
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
}
}
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
}
})
})
})
}
}
}