Send data Between iOS Apps and Extensions Using Darwin Notifications
In iOS development, app extensions run in separate processes from their containing apps. This separation poses a challenge when you need to communicate between the main app and its extensions. While NSNotificationCenter
is a common choice for passing data between view controllers within the same app, it falls short when it comes to inter-process communication. Have you ever thought about how to pass data between the main app and its extension? Darwin notifications provide a powerful solution for this scenario. In this post, we’ll explore how to implement a Darwin Notifications manager and use it to facilitate real-time data transfer between a main app and its extension.
What are Darwin Notifications a.k.a CFNotificationCenterGetDarwinNotifyCenter?
CFNotificationCenterGetDarwinNotifyCenter
is a function in Apple’s Core Foundation framework that provides access to the Darwin Notification Center. This notification center is designed for system-wide notifications, allowing different processes (such as your app and its extensions) to communicate with each other.
How Does it Work?
System-Wide Communication: Unlike NSNotificationCenter, which is limited to the app’s process, the Darwin Notification Center can send notifications that can be observed by other processes on the device. This makes it ideal for app-to-extension communication.
No UserInfo Dictionary: One limitation is that Darwin notifications do not support sending additional data (like a userInfo dictionary). This means you can only send a simple notification without any extra information. This is because the underlying mechanism, notify_post(), only accepts a string identifier for the notification
A use case for Darwin Notifications
For example, when a broadcast upload extension starts or stops, you can use a Darwin notification to inform the main app. I have seen most of the people use UserDefaults or the Keychain but I personally feel that Darwin notifications are the best fit for this use case.
Implementing the Darwin Notification Manager
To start, we’ll create a DarwinNotificationManager
class that uses the CFNotificationCenter
API to post and observe notifications across processes.
import Foundation
class DarwinNotificationManager {
static let shared = DarwinNotificationManager()
private init() {}
private var callbacks: [String: () -> Void] = [:]
func postNotification(name: String) {
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(name as CFString), nil, nil, true)
}
func startObserving(name: String, callback: @escaping () -> Void) {
callbacks[name] = callback
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterAddObserver(notificationCenter,
Unmanaged.passUnretained(self).toOpaque(),
DarwinNotificationManager.notificationCallback,
name as CFString,
nil,
.deliverImmediately)
}
func stopObserving(name: String) {
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterRemoveObserver(notificationCenter, Unmanaged.passUnretained(self).toOpaque(), CFNotificationName(name as CFString), nil)
callbacks.removeValue(forKey: name)
}
private static let notificationCallback: CFNotificationCallback = { center, observer, name, _, _ in
guard let observer = observer else { return }
let manager = Unmanaged<DarwinNotificationManager>.fromOpaque(observer).takeUnretainedValue()
if let name = name?.rawValue as String?, let callback = manager.callbacks[name] {
callback()
}
}
}
To understand more about Darwin Notifications, please refer to the original article: ‘Sending Data Between iOS Apps and Extensions Using Darwin Notifications’ available at this link: https://ohmyswift.com/blog/2024/08/27/send-data-between-ios-apps-and-extensions-using-darwin-notifications/
Thanks for reading!