如何在包含扩展的iOS应用程序和扩展之间进行通信(不是主机应用程序)

32

简言之:是否可能在iOS应用程序和其扩展之间发送实时消息或通知?

我正在编写一个iOS应用程序,其中包括一个扩展,它们都属于同一App Group并共享相同的CoreData(SQLite数据库)。我可以从应用程序和扩展中使用CoreData读取和写入数据库,它们都共享相同的内容。

我的问题是:是否可能在应用程序和扩展之间发送消息或通知以便在必要时通知另一个更新?

我尝试通过NSNotificationCenter发送通知,但这不会“离开”应用程序/扩展,如果我尝试写入组共享的NSUserDefaults并监听NSUserDefaultsDidChangeNotification,则有相同的问题。这在应用程序内部可以工作,但扩展没有收到任何东西(当我知道它已启动并且共享相同的NSUserDefaults时)。有什么想法可以使事情保持同步吗?


还可以查看文档:与包含应用程序共享数据 - mfaani
4个回答

39
TLDR: 不行,但有一种方法可以“hack”。
iOS应用程序无法真正地进行进程间通信,无论是否使用扩展。NSDistributedNotification从OS X到iOS仍未移植,并且可能永远不会被移植。
使用某些扩展类型,您可以通过NSExtensionContext打开URL,并将它们用于将数据传递给处理URL的应用程序。这会使该应用程序进入前台,这似乎并不是您想要的。
然而,有一种hack可能会让你得到所需的内容。
- 不要直接编写用户默认值,而是编写一个文件到您的应用程序组目录中。 - 不要直接编写文件--使用NSFileCoordinator对文件进行协调写入。 - 在想要了解文件更改的对象上实现NSFilePresenter,并确保调用[NSFileCoordinator addFilePresenter:someObject]。 - 在文件呈现器上实现可选的presentedItemDidChange方法。
如果您做得正确,您可以从应用程序或扩展中写入此文件,然后在另一个应用程序中自动调用presentedItemDidChange。作为奖励,您当然可以读取该文件的内容,以便可以来回传递任意数据。

我曾尝试使用open()和一些标志来监视文件系统的更改,但没有成功。我不知道NSFilePresenter!这个方法完美地解决了问题,谢谢! - Ludovic Landry
1
Ludovic Landry,您可以给个例子说明您是如何成功实现那种通信的吗?非常感谢。 - Lee Andrew
如果我的包含应用程序没有运行(已终止),presentedItemDidChange 方法会启动/唤醒包含应用程序吗? - marcelosalloum
1
如果应用程序没有运行,它就无法接收通知。不过,您的应用程序在启动时需要加载文件内容,因此它不需要该通知来查找最新数据。 - Tom Harrington

15

在iOS中,你可以使用一种技巧在任意两个应用程序之间或应用程序和扩展之间进行通信。唯一的限制是它不能与NetworkExtension一起使用,因为苹果公司会阻止其中的任何I/O操作。

你可以通过以下方式将通知发布到DarwinNotificationCenter:

    let notificationName = CFNotificationName("com.notification.name" as CFString)
    let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

    CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)
在您的应用程序中添加观察者:
    let notificationName = "com.notification.name" as CFString
    let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

    CFNotificationCenterAddObserver(notificationCenter,
                                    nil,
                                    { (
                                        center: CFNotificationCenter?,
                                        observer: UnsafeMutableRawPointer?,
                                        name: CFNotificationName?,
                                        object: UnsafeRawPointer?,
                                        userInfo: CFDictionary?
                                        ) in

                                        print("Notification name: \(name)")
                                    },
                                    notificationName,
                                    nil,
                                    CFNotificationSuspensionBehavior.deliverImmediately)

一些链接: https://github.com/choefele/CCHDarwinNotificationCenter

https://developer.apple.com/documentation/corefoundation/1542572-cfnotificationcentergetdarwinnot

https://developer.apple.com/library/content/documentation/Darwin/Conceptual/MacOSXNotifcationOv/DarwinNotificationConcepts/DarwinNotificationConcepts.html


2
好的,只是一个警告 - 你不能在回调函数中访问"self"。有一种使用void指针访问它的方法,但这也不完美,会出现"EXC_BAD_ACCESS"错误,这些错误无法被捕获和处理(虽然可能可以使用这个:https://dev59.com/62Qo5IYBdhLWcg3wQNb4#55179440)。但你可以访问类本身,也就是说,你可以将类的实例作为静态变量存储,从那里调用你的方法。 - Starwave
请注意首先检查实例是否存在。此外,由于其引用仍然被硬绑定到静态变量中,因此该类将无法进行deinit()。因此,它将一直存在于您的内存中。只有在覆盖/清空静态实例变量或手机内存不足并开始杀死左右的东西时,它才会被解除初始化。 - Starwave
2
不要尝试访问“self”,而是在回调函数中发布一个NotificationCenter,然后在应用程序的任何地方添加观察者。这对我很有效 :) - sharon
2
我尝试过,但当应用程序在后台时它并没有起作用。我从iOS 14小部件扩展中向应用程序发布了一个通知。当我将应用程序打开到前台时,通知被传递了,但之前没有。我认为这需要使用后台模式才能实现。 - Emma Labbé

9

如果想要在主机应用程序和应用扩展之间执行通用的双向通信,可以尝试使用MMWormhole

http://www.mutualmobile.com/posts/mmwormhole https://github.com/mutualmobile/MMWormhole

它是CFNotificationCenter的一个相当轻量级的包装器,并使用“Darwin”通知进行进程间通信(IPC)。

它通过应用程序共享容器传递有效负载,并且甚至封装了创建文件本身的过程。

该类(以及回购中的示例应用程序)似乎工作良好并且响应迅速。

希望这也有所帮助。


2

我一直在苦苦挣扎,但也没有找到一个干净的解决方案。另一种解决方法是在扩展中运行定时器,并定期检查共享容器首选项/数据库中的值,然后根据需要进行更新。虽然不太优雅,但似乎可以解决问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接