I am writing a Safari app extension and want to fetch the URL for the active page in my view controller.
This means nested completion handlers to fetch the window, to fetch the tab, to fetch the page, to access its properties. Annoying but simple enough. It looks like this:
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))")
}
The obvious problem is it does not work. Being completion handlers they will not be executed until the end of the function. The variable url
will be nil, and the stuff will be done before any attempt is made to get the URL.
One way around this is to use a DispatchQueue
. It works, but the code is truly ugly:
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))")
}
}
The if
blocks are needed to know we are not dealing with a nil value. We need to be certain a completion handler will return, and therefore a .leave()
call before we can call a .enter()
to end up back at zero.
I cannot even bury all that ugliness away in some kind of getURLForPage()
function or extension (adding some kind of SFSafariApplication.getPageProperties
would be my preference) as obviously you cannot return from a function from within a .notify
block.
Although I tried creating a function using queue.wait
and a different DispatchQueue
as described in the following answer to be able to use return…
https://stackoverflow.com/a/42484670/2081620
…not unsurprisingly to me it causes deadlock, as the .wait
is still executing on the main queue.
Is there a better way of achieving this? The "stuff to do," incidentally, is to update the UI at a user request so needs to be on the main queue.
Edit: For the avoidance of doubt, this is not an iOS question. Whilst similar principles apply, Safari app extensions are a feature of Safari for macOS only.