Omid Ariyan
Omid Ariyan

Reputation: 1340

Can an application tune in to Core Data changes made by an extension?

I'm working on an application which uses a shared Core Data database between itself and a Notification Service Extension. Both the application and the extension are able to read and write to the same Core Data database.

The application however needs to update the displayed information as soon as the corresponding fields change in the database. Is there an efficient way for it to be notified of the changes the extension makes to the database? I assume the application and the extension use different managed contexts to access the database. Or am I wrong?

Upvotes: 2

Views: 566

Answers (2)

Cerlin
Cerlin

Reputation: 6722

Using SwiftEventBus this is pretty straight forward

Controller.swift

let yourObj = YourObject()

SwiftEventBus.post("EventName", sender: yourObj)

Extension.swift

let yourObj = YourObject()

SwiftEventBus.post("EventName", sender: yourObj)

AppDelegate.swift

SwiftEventBus.onMainThread(self, name: "EventName") { (result) in
    if let yourObject = result.object as? YourObject {
        // Queue or write the data as per your need
    }
}

Upvotes: 1

Omid Ariyan
Omid Ariyan

Reputation: 1340

I found a solution to the problem I described after being pointed towards using notification by @CerlinBoss. It is possible to send a notification from the extension to the application (or vice versa). This can be done in iOS using a Darwin notification center. The limitation however is that you can't use the notification to send custom data to your application.

After reading many articles I decided that I'd avoid making changes to the Core Data database from two different processes and using multiple managed contexts. Instead, I queue the data I need to communicate to the application inside a key in the UserDefaults and once the application is notified of the changes, I'd dequeue them and update the Core Data context.

Common Code

Swift 4.1

import os
import Foundation

open class UserDefaultsManager {

  // MARK: - Properties

  static let applicationGroupName = "group.com.organization.Application"

  // MARK: - Alert Queue Functions

  public static func queue(notification: [AnyHashable : Any]) {
    guard let userDefaults = UserDefaults(suiteName: applicationGroupName) else {
      return
    }
    // Retrieve the already queued notifications.
    var alerts = [[AnyHashable : Any]]()
    if let data = userDefaults.data(forKey: "Notifications"),
      let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
      alerts.append(contentsOf: items)
    }
    // Add the new notification to the queue.
    alerts.append(notification)
    // Re-archive the new queue.
    let data = NSKeyedArchiver.archivedData(withRootObject: alerts)
    userDefaults.set(data, forKey: "Notifications")
  }

  public static func dequeue() -> [[AnyHashable : Any]] {
    var notifications = [[AnyHashable : Any]]()
    // Retrieve the queued notifications.
    if let userDefaults = UserDefaults(suiteName: applicationGroupName),
      let data = userDefaults.data(forKey: "Notifications"),
      let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [[AnyHashable : Any]] {
      notifications.append(contentsOf: items)
      // Remove the dequeued notifications from the archive.
      userDefaults.removeObject(forKey: "Notifications")
    }
    return notifications
  }
}

Extension:

Swift 4.1

  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    if let bestAttemptContent = bestAttemptContent {
      os_log("New notification received! [%{public}@]", bestAttemptContent.body)
      // Modify the notification content here...
      // Queue the notification and notify the application to process it
      UserDefaultsManager.queue(notification: bestAttemptContent.userInfo)
      notifyApplication()
      contentHandler(bestAttemptContent)
    }
  }

  func notifyApplication() {
    let name: CFNotificationName = CFNotificationName.init("mutableNotificationReceived" as CFString)
    if let center = CFNotificationCenterGetDarwinNotifyCenter() {
      CFNotificationCenterPostNotification(center, name, nil, nil, true)
      os_log("Application notified!")
    }
  }

Application:

Swift 4.1

  // Subscribe to the mutableNotificationReceived notifications from the extension.
  if let center = CFNotificationCenterGetDarwinNotifyCenter() {
    let name = "mutableNotificationReceived" as CFString
    let suspensionBehavior = CFNotificationSuspensionBehavior.deliverImmediately
    CFNotificationCenterAddObserver(center, nil, mutableNotificationReceivedCallback, name, nil, suspensionBehavior)
  }

  let mutableNotificationReceivedCallback: CFNotificationCallback = { center, observer, name, object, userInfo in
    let notifications = UserDefaultsManager.dequeue()
    for notification in notifications {
      // Update your Core Data contexts from here...
    }
    print("Processed \(notifications.count) dequeued notifications.")
  }

Upvotes: 0

Related Questions