以下はiOS8.1で試した結果です。
NCWidgetProvidingを実装したViewController(以下WidgetViewController)は、Widgetの表示時に生成され、ユーザがWidgetを閉じると破棄される。
しかし、widgetPerformUpdateWithCompletionHandler()のcompletionHandlerを呼ぶ前にWidgetを閉じると、completionHandlerが呼ばれるまで破棄を待つ。約10秒待ってもcompletionHandlerが呼ばれない場合、そのままWidgetViewControllerを破棄する。
この状態になると、その後でcompletionHandlerを呼んでも、次回以降二度とwidgetPerformUpdateWithCompletionHandler()は呼ばれなくなる。
これを回避するためにいろいろ試した。
- selfをキャプチャして破棄を防いでみる
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
NSLog("widgetPerformUpdateWithCompletionHandler")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
NSThread.sleepForTimeInterval(20)
dispatch_async(dispatch_get_main_queue()) {
self.finish(completionHandler)
}
}
}
func finish(completionHandler: ((NCUpdateResult) -> Void)!) {
println("completionHandler")
completionHandler(.NoData)
}
これだとcompletionHandlerを呼んだ後にselfが破棄されるが、widgetPerformUpdateWithCompletionHandler()が呼ばれなくなる現象は直らない。
たぶんシステムがWidgetViewControllerへの参照を外した時点でcompletionHandlerが呼ばれていなければダメなのでは。
- viewWillDisappear()でcompletionHandlerを呼ぶ
private var handler: ((NCUpdateResult) -> Void)?
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if let block = handler {
NSLog("viewWillDisappear completionHandler")
block(.NoData)
handler = nil
}
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
NSLog("widgetPerformUpdateWithCompletionHandler")
handler = completionHandler
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { [weak self] in
NSThread.sleepForTimeInterval(20)
dispatch_async(dispatch_get_main_queue()) {
NSLog("completionHandler")
if let block = self?.handler {
NSLog("block")
block(.NoData)
self?.handler = nil
}
}
}
}
これだとWidgetを閉じたときにcompletionHandlerを呼ぶことができ、widgetPerformUpdateWithCompletionHandler()が呼ばれなくなることを回避できる。
実際はNSThread.sleepForTimeInterval()の部分に更新処理が入るのだが、viewWillDisappear()でキャンセルするのがよさそう。
- まとめ
widgetPerformUpdateWithCompletionHandler()のcompletionHandlerは、Widgetが非表示になってから約10秒以内に呼ぶ必要がある。そうしなければそれ以降widgetPerformUpdateWithCompletionHandler()は呼ばれなくなる。
この仕様はOSのアップデートで変わる可能性があるので、更新処理はviewWillAppear()でやったほうが無難かも。