Update my answer
Conclusion first
Currently WKWatchConnectivityRefreshBackgroundTask
is only called for sure on a watchOS Simulator when the watchOS extension's WCSession
is in notActivated
state and the extension is not running in foreground (in background or terminated).
In real devices, it won't be called in my tests. But Apple docs says it may. So you shouldn't rely on it won't be called until Apple changes its docs.
WCSession Cores
For WCSession
, when it is activated
, you can transfer userInfo, and when the counterpart is active, it can get the userInfo. The counterpart won't need to be in foreground to be activated, it can be in a high priority background.
Testing Results
Here are my testing results.
How to make WCSession
notActivated
?
- Using Xcode terminate your watchOS Extension. Xcode will send a kill signal to your WKExtension.
- Or don't run
WCSession.activate()
in your code on watchOS Extension side. As WCSession
is notActivated
by default.
-------------below are old post, you can ignore safely if you don't want to read.-------------------
Theory
Please watch the picture first then I will explain.
Because of the history of watchOS, there are both WCSessionDelegate
receiving functions (start from watchOS 2.0) and WKExtensionDelegate.handle(_:)
function (start from watchOS 3.0).
Although they all claims to be background dealing, the former only works immediately when your app is in foreground. The data will be queued if your app is not in foreground (in background or being terminated) and executed immediately later when your app becomes in foreground again.
WKExtensionDelegate.handle(_:)
is really working in background. However, the WKExtensionDelegate.handle(_:)
is optional although it is recommended and well-prepared if you use Xcode.
If you don't implement WKExtensionDelegate.handle(_:)
by commenting it. You app works in a watchOS 2.0 way.
If you implement WKExtensionDelegate.handle(_:)
but you don't have a WCSession
in your watchOS app. The result is tricky. You won't get any data when you watchOS app is in foreground, as you don't has a WCSession
. When your app is in background, it will be waken when data comes, but you can't get the data as you don't have a session.
If you implemented them both, which are in most situations, data comes will be dealt depending on the state of your watchOS app and never be queued.
How to proving it?
Create a new watchOS project. In iOS part, add a button, each time you clicked the button, send a userInfo to watchOS
session.transferUserInfo(["send test":""])
In your watchOS app, add a label in interface.storyboard
, and drag it to viewController
as @IBOutlet var label: WKInterfaceLabel!
, and implement both WKExtensionDelegate.handle(_:)
and func session(WCSession, didReceiveUserInfo: [String : Any] = [:])
appDelegate
.
var total = 0
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background task once you’re done.
backgroundTask.setTaskCompleted()
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
// Snapshot tasks have a unique completion call, make sure to set your expiration date
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
// Be sure to complete the connectivity task once you’re done.
total += 1
DispatchQueue.main.async {
if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
viewController.label.setText(String(self.total))
}
}
connectivityTask.setTaskCompleted()
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once you’re done.
urlSessionTask.setTaskCompleted()
default:
// make sure to complete unhandled task types
task.setTaskCompleted()
}
}
}
public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
total += 4
DispatchQueue.main.async {
if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
viewController.label.setText(String(self.total))
}
}
}
If WKExtensionDelegate.handle(_:)
runs, we add total
by 1. If func session(WCSession, didReceiveUserInfo: [String : Any] = [:])
runs, we add total
by 4.
Debug
In Xcode, choose product->scheme
as WatchKit app
so we can terminate the watchOS app in Xcode.
- run the project.
- when watchOS app shows, open iOS app manually.
- clicked the button in iOS app. You can see the
label
in watchOS changes by 4.
- in Xcode, click
product->stop
(or cmd+.). watchOS app will disappear.
- click one or more times on iOS app's button. Then manually open the watchOS app. You will see this time the
label
changes by 1 multiply your clicks.
- The step will be 4 again when watchOS app is in foreground.