主页 CFNotification进程间通讯
Post
Cancel

CFNotification进程间通讯

前言

本文具有强烈的个人感情色彩,如有观看不适,请尽快关闭. 本文仅作为个人学习记录使用,也欢迎在许可协议范围内转载或分享,请尊重版权并且保留原文链接,谢谢您的理解合作. 如果您觉得本站对您能有帮助,您可以使用RSS方式订阅本站,感谢支持!

背景

在iOS开发中,应用扩展(App Extensions)与其容器应用(Containing App)在不同的进程中运行。这种隔离带来了一个挑战:当你需要在主应用和它的扩展之间进行通信时。虽然NSNotificationCenter是在同一应用内传递数据给不同视图控制器的常见选择,但在处理进程间通信时就力不从心了。你是否曾想过如何在主应用及其扩展之间传递数据?Darwin通知为这一场景提供了一个强大的解决方案。在本文中,我们将探讨如何实现一个Darwin通知管理器,并使用它来促进主应用和其扩展之间的实时数据传输。

什么是Darwin通知(又称CFNotificationCenterGetDarwinNotifyCenter)? CFNotificationCenterGetDarwinNotifyCenter是苹果Core Foundation框架中的一个函数,提供了访问Darwin通知中心的权限。这个通知中心被设计用于系统级的通知,允许像你的应用和其扩展这样的不同进程彼此通信。

它是如何工作的? 系统级通信:与仅仅限于应用内部的NSNotificationCenter不同,Darwin通知中心可以发送能够被设备上其他进程观察到的通知。

Darwin Notifications,也称为CFNotificationCenterGetDarwinNotifyCenter,是什么?

CFNotificationCenterGetDarwinNotifyCenter是苹果公司Core Foundation框架中的一个函数,它提供了对Darwin通知中心的访问权限。该通知中心被设计用于系统级的通告,允许不同进程(例如你的应用及其扩展)彼此通信。

它是如何工作的?

系统级通信:与仅限于应用进程的NSNotificationCenter不同,Darwin通知中心可以发送能被设备上其他进程观察到的通知。这对于应用间以及应用到扩展的通信来说是一个理想的选择。

不支持userInfo字典:一个限制是Darwin通知不支持发送额外的数据(如userInfo字典)。这意味着你只能发送一个简单的通知,没有任何额外信息。这是因为底层机制notify_post()只接受一个字符串标识符作为通知。

Darwin通知的一个用例是

例如,当广播上传扩展开始或停止时,你可以使用Darwin通知来通知主应用。我看到大多数人使用UserDefaults或Keychain,但我个人认为Darwin通知最适合这个用例。

实现Darwin通知管理器

首先,我们将创建一个DarwinNotificationManager类,使用CFNotificationCenter API来跨进程发布和观察通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import Foundation

class DarwinNotificationManager {
    
    static let shared = DarwinNotificationManager()
    
    private init() {}
    
    // 1
    private var callbacks: [String: () -> Void] = [:]
    
    // Method to post a Darwin notification
    func postNotification(name: String) {
        let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
        CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(name as CFString), nil, nil, true)
    }
    
    // 2
    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)
    }
    
    // 3
    func stopObserving(name: String) {
        let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
        CFNotificationCenterRemoveObserver(notificationCenter, Unmanaged.passUnretained(self).toOpaque(), CFNotificationName(name as CFString), nil)
        callbacks.removeValue(forKey: name)
    }
    
    // 4
    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()
        }
    }
}

Darwin通知管理器的解析

1
2
private var callbacks: [String: () -> Void] = [:]

回调callbacks 函数存储每个通知名称的回调,以便在收到通知时执行。

1
2
3
4
5
6
7
8
9
10
11
12
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)
}

startObserving方法为通知注册一个回调,并添加观察者来监听它。当你想开始监听通知时调用这个方法。它通常在视图初始化时被调用。

1
2
3
4
5
func stopObserving(name: String) {
    let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
    CFNotificationCenterRemoveObserver(notificationCenter, Unmanaged.passUnretained(self).toOpaque(), CFNotificationName(name as CFString), nil)
    callbacks.removeValue(forKey: name)
}

stopObserving方法移除通知的观察者并删除其回调,以停止监听。它通常在视图被释放时调用。

1
2
3
4
5
6
7
8
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()
    }
}

notificationCallback在收到相应的通知时执行存储的回调。

在广播扩展中使用管理器

1
2
3
4
5
6
7
8
9
10
11
12
import ReplayKit

class SampleHandler: RPBroadcastSampleHandler {
    
    override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
        DarwinNotificationManager.shared.postNotification(name: "com.yourapp.BroadcastStarted")
    }
    
    override func broadcastFinished() {
        DarwinNotificationManager.shared.postNotification(name: "com.yourapp.BroadcastStopped")
    }
}

在UIKit视图中观察Darwin通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class DashboardViewController: UIViewController { 
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.configureCallbacks()
    }


    fileprivate func configureCallbacks() {
        DarwinNotificationManager.shared.startObserving(name: "com.yourapp.BroadcastStarted") {
            print("*******Broadcast has started*******")
            // Handle the event when broadcast starts
        }
        
        DarwinNotificationManager.shared.startObserving(name: "com.yourapp.BroadcastStopped") {
            print("*******Broadcast has stopped*******")
            // Handle the event when broadcast starts
        }

    }
}

在SwiftUI视图中观察Darwin通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import SwiftUI
import Foundation

struct BroadcastView: View {
    @State private var broadcastStatus: String = "Not Broadcasting"
    
    var body: some View {
        VStack {
            Text(broadcastStatus)
                .font(.largeTitle)
                .padding()
        
        }
        .onAppear {
            configureCallbacks()
        }
        .onDisappear {
            stopCallbacks()
        }
    }
    
    private func configureCallbacks() {
        DarwinNotificationManager.shared.startObserving(name: "com.yourapp.BroadcastStarted") {
            broadcastStatus = "Broadcasting..."
            print("*******Broadcast has started*******")
        }
        
        DarwinNotificationManager.shared.startObserving(name: "com.yourapp.BroadcastStopped") {
            broadcastStatus = "Not Broadcasting"
            print("*******Broadcast has stopped*******")
        }
    }
    
    private func stopCallbacks() {
        DarwinNotificationManager.shared.stopObserving(name: "com.yourapp.BroadcastStarted")
        DarwinNotificationManager.shared.stopObserving(name: "com.yourapp.BroadcastStopped")
    }
}

正如我之前提到的,在onAppear中开始观察,在onDisappear中停止观察。这将确保你的代码不会导致内存泄漏。

关键要点

1.进程间通信:Darwin通知为不同进程之间的通信提供了一种强大的机制,如主应用与其扩展之间的通信,克服了NSNotificationCenter的限制。

2.系统级覆盖:与仅限于单个应用的NSNotificationCenter不同,Darwin通知可以被设备上的任何进程观察到,使它们成为应用-扩展通信的理想选择。

3.不支持有效负载:Darwin通知不支持发送额外的数据(如userInfo字典)。它们仅限于通知观察者事件发生,没有额外的上下文。

4.高效的通知处理:通过使用CFNotificationCenterGetDarwinNotifyCenter,开发人员可以有效地发布和观察通知,无需管理额外数据的开销。

5.SwiftUI集成Darwin通知可以轻松集成到SwiftUI应用程序中,允许在不同应用组件之间进行实时更新和状态管理。

总结

我的脑海里总是将这些进程间通知比作彗星穿过我们的地球。正如彗星是罕见且令人敬畏的事件,吸引我们的注意力一样,Darwin通知作为关键的信号,让应用生态系统的不同部分能够无缝通信。这难道不是一个很好的比喻吗?

所以,就是这样了。如果你一直读到这里,我真的很感激。我希望这次对Darwin通知的探索能激发你以新的方式思考进程间通信。我迫不及待想看到你如何在应用中使用Darwin通知。请在下面的评论中告诉我你的想法和经验。你的反馈和想法总是受欢迎的!也别忘了与你的网络分享这篇文章!

原文链接 Send data Between iOS Apps and Extensions Using Darwin Notifications
CFNotificationCenter文档

该博客文章由作者通过 CC BY 4.0 进行授权。

在Objective-C中多层block嵌套时如何使用weak self和strong self

UIImage镜像