In didFinishLaunchingWithOptions I have this setup for getting the token to send to my server for notifications. The issue is that the delegate callback didRegisterForRemoteNotificationsWithDeviceToken gets called twice when also initializing a CKSyncEngine object.
This confuses me. Is this expected behavior? Why is the delegate callback only called twice when both are called, but not at all when only using CKSyncEngine.
See code and comments below.
/// Calling just this triggers `didRegisterForRemoteNotificationsWithDeviceToken` once.
UIApplication.shared.registerForRemoteNotifications()
/// When triggering the above function plus initializing a CKSyncEngine, `didRegisterForRemoteNotificationsWithDeviceToken` gets called twice.
/// This somewhat make sense, because CloudKit likely also registers for remote notifications itself, but why is the delegate not triggered when *only* initializing CKSyncEngine and removing the `registerForRemoteNotifications` call above?
let syncManager = SyncManager()
/// Further more, if calling `registerForRemoteNotifications` with a delay instead of directly, the delegate is only called once, as expected. For some reason, the delegate is only triggered when two entities call `registerForRemoteNotifications` at the same time?
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
UIApplication.shared.registerForRemoteNotifications()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("didRegisterForRemoteNotificationsWithDeviceToken")
}
You might follow up with your report to see what the APNs folks have to say, but I believe the behavior is expected, and here is why:
a. When you call registerForRemoteNotifications, the system kicks off the registration, and delivers the device token to you by calling didRegisterForRemoteNotificationsWithDeviceToken when the registration is done successfully.
b. When you create a CKSyncEngine instance, CloudKit automatically registers for remote notifications because it relies on APNs to synchronize data. At the moment of that being done, if you've done registerForRemoteNotifications, which shows that you are interested in the device token, CloudKit delivers the device token to you by calling didRegisterForRemoteNotificationsWithDeviceToken.
So if you successfully do registerForRemoteNotifications and then create a CKSyncEngine instance, you'd see that the device token is delivered twice.
You can avoid that by calling registerForRemoteNotifications after you finish creating your CKSyncEngine instance, as shown below:
final actor SyncManager {
...
init() {}
func initializeSyncEngine() {
var configuration = CKSyncEngine.Configuration(
database: Self.container.privateCloudDatabase,
stateSerialization: nil,
delegate: self
)
let syncEngine = CKSyncEngine(configuration)
_syncEngine = syncEngine
}
…
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let syncManager = SyncManager()
Task {
await syncManager.initializeSyncEngine()
UIApplication.shared.registerForRemoteNotifications()
}
return true
}
...
}
Note that in your code attached to your feedback report, SyncManager.init uses Task to to create a CKSyncEngine instance, which doesn't guarantee that the creation is done before registerForRemoteNotifications is called. The above code refactored the race away.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.