Handling widgetPerformUpdate in an iOS Today extension
Apple's documentation about the NCWidgetProviding
protocol, and widgetPerformUpdate:
function in particular are rather sparse, and most posts on StackOverflow seem to have a very simplistic view about how this callback should be coded. So here are my tips.
Most examples you'll see for widgetPerformUpdate:
look rather like this:
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
let resultModel = performExpensiveNetworkOperation()
label.text = resultModel.someValue
completionHandler(NCUpdateResult.newData)
}
... which would probably be OK, most of the time, but there are very few examples of expensive work being done asynchronously.
There are, however, three big hints that this is the right thing to do:
- Apple's documentation says "It's expected that the widget will perform the work to update asynchronously and off the main thread as much as possible". (The
widgetPerformUpdate:
call does indeed arrive on the main thread) - the
completionHandler
block is annotated@escaping
, implying that it will outlive the scope of thewidgetPerformUpdate:
function itself - the function returns
Void
; if this were intended to be a blocking call, then the func would simply have anNCUpdateResult
return value instead of providing a completion block
Doing it right
The correct way to do this depends on whether your long-running operation is synchronous (blocking), or asychronous with a completion block, or asynchronous with a delegate. Here are examples of all three.
Performing a synchronous (blocking) operation
class SynchronousCallExampleViewController: UIViewController, NCWidgetProviding {
private let dataSource = DataSource()
private var data: Data?
private func updateUI() {
DispatchQueue.main.async {
// update UI components
}
}
func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void) {
DispatchQueue.global().async {
// fetch data on a background thread
self.data = self.dataSource.fetchData()
self.updateUI()
completionHandler(.newData)
}
}
}
Performing an asynchronous operation with a completion block
class AsynchronousCallWithCompletionBlockExampleViewController: UIViewController, NCWidgetProviding {
private let dataSource = DataSource()
private var data: Data?
private func updateUI() {
DispatchQueue.main.async {
// update UI components
}
}
func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void) {
DispatchQueue.global().async {
// fetch data on a background thread
self.dataSource.fetchDataAsync({ [weak self] (data) in
guard let self = self else { return }
self.data = data
self.updateUI()
completionHandler(.newData)
})
}
}
}
Performing an asynchronous operation with a delegate callback
class AsynchronousCallWithDelegateExampleViewController: UIViewController, NCWidgetProviding, DataSourceDelegate {
private var dataSource = DataSource()
private var widgetCompletionHandler: ((NCUpdateResult) -> Void)?
private var data: Data?
private func updateUI() {
DispatchQueue.main.async {
// update UI components
}
}
func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void) {
widgetCompletionHandler = completionHandler
dataSource.delegate = self
DispatchQueue.global().async {
// fetch data on a background thread
self.dataSource.fetchDataAsync()
}
}
func dataDidUpdate(dataSource: DataSource) {
data = dataSource.data
updateUI()
if let completionHandler = widgetCompletionHandler {
completionHandler(.newData)
widgetCompletionHandler = nil
}
}
}
First published 10 November 2018