在扩展之间共享UserDefaults

83

我正在创建一个今日小组件,使用UserDefaults(suiteName:)来存储一些数据。在主应用程序中,我正在使用UserDefaults.standard()。扩展程序无法读取(或者说它可以读取吗?),这就是为什么我使用了suiteName:构造函数。

用户将数据持续到主应用程序中的UserDefaults.standard(),需要在扩展程序中可用。

目前我正在同时进行持久化,以便可以共享值。

 UserDefaults.standard().set:...forKey:...
 UserDefaults(suiteName:...)().set:...forKey:...
 ...
问题是我是否应该完全放弃使用 UserDefaults.standard(),并仅在我的应用程序中使用 UserDefaults(suiteName:),或者这是一种不好的做法,如果是为什么?

编辑:我正在使用一个App组容器。 为澄清起见,我想知道是否应该在整个项目中将 standard() 替换为 suiteName:


7
如果你想在扩展和应用之间共享资源,最好的方法是创建一个「App Group」并使用共享的「UserDefault」(通过「-suiteName:」)或默认的「FileManager」(通过「-forSecurityApplicationGroupIdentifier:」),你可能不再需要标准的用户默认设置或文件管理器(但如果你需要确保从扩展中隔离数据,仍然可以使用这些沙盒)。 - holex
6个回答

84
您无法使用UserDefaults.standard在主应用程序和其应用程序扩展之间共享数据。您必须使用UserDefaults(suiteName:)创建共享容器来共享数据。
虽然应用程序扩展bundle嵌套在其包含的应用程序bundle中,但运行的应用程序扩展和包含的应用程序彼此没有直接访问对方的容器。
要启用数据共享,请使用Xcode或开发者门户为包含应用程序及其包含的应用程序扩展启用应用组。接下来,在门户中注册应用程序组并指定要在包含应用程序中使用的应用程序组。
启用应用程序组后,应用程序扩展及其包含的应用程序都可以使用NSUserDefaults API来共享用户偏好设置访问权限。要启用此共享,请使用initWithSuiteName:方法实例化一个新的NSUserDefaults对象,并传递共享组的标识符。
有关详细信息,请参阅:https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1 如何使用应用程序组:https://github.com/pgpt10/Today-Widget 标准版或套件名?
为仅适用于主机应用程序的数据使用标准版。为要在扩展和主机应用程序之间共享的数据使用suiteName。只是不要在两者中都持久化相同的数据。避免数据冗余。根据上下文同时使用两者。

抱歉,我认为使用“initSuiteName:”就已经让人觉得我在使用应用程序组。我确实在使用。更新了OQ以澄清这一点。谢谢。 - RyanTCB
那么你面临的问题是什么? - PGDev
1
我目前将相同的数据持久化到标准和suiteName构造函数中。我应该放弃standard()并使用suiteName吗? - RyanTCB
2
使用标准的数据仅供主机应用程序使用。使用suiteName共享扩展和主机应用程序之间的数据。只是不要在两者中都持久化相同的数据。避免数据冗余。根据上下文使用它们两个。 - PGDev
(这个回答中的“主机应用程序”是不是实际上应该指的是“容器应用程序”?) - benc
我已经成功实现了你的解决方案,但是,如果我需要让应用程序根据扩展的生命周期执行某些操作。 比如,在自定义键盘扩展的viewWillAppear时调用chatGPT API,在键盘的viewWillDisappear时停止服务,如何处理这样的情况呢? - undefined

80

确保在Capabilities选项卡中为所有目标(您的应用程序和扩展目标)启用App Groups

enter image description here

然后在创建UserDefaults时,使用上面的组标识符作为套件名称:

let userDefaults = UserDefaults(suiteName: "group.com.YourCompany.YourApp")

8
这一步非常重要。 - P. Ent
我们可以将任何名称添加到组标识符中吗? - user4150758
谢谢 Tai Le。我被卡在这里了。 @user4150758 我们必须将 suiteName 设置为与 App Groups 名称相同。否则它不会起作用。 - Chirag Kalsariya

48

我创建一个共享捆绑包的简单示例:

if let userDefaults = UserDefaults(suiteName: "group.your.bundle.here") {
    userDefaults.set("test 1" as AnyObject, forKey: "key1")
    userDefaults.set("test 2" as AnyObject, forKey: "key2")
    userDefaults.synchronize()
}

这是您稍后可以阅读它的方式:

if let userDefaults = UserDefaults(suiteName: "group.your.bundle.here") {
    let value1 = userDefaults.string(forKey: "key1")
    let value2 = userDefaults.string(forKey: "key2")
    ...
}

10
您不需要调用synchroniz - BilalReffas
1
这实际上并没有回答应该使用哪一个的问题。如果你按照你所写的去做,如果数据是使用UserDefaults.standard()从主机应用程序保存的话,它将不起作用。 - Anuran Barman
谢谢,这很有帮助。我喜欢有代码支持的答案。 - N J

14

同时确保将 App Groups 添加到正确的配置(Debug、Release)中。如果您在应用程序目标的调试配置中添加了 App Groups 并尝试在扩展的发布配置中使用它,则无法正常工作。

如果您在应用程序目标的调试配置中添加,那么请在扩展目标的调试配置中使用。


这个问题实际上很简单,可我浪费了一个小时。感谢你指出来。 - Swift Sharp
我真希望在浪费了三个小时之前读过这篇文章... 我只把我的扩展组放在了个人资料上... - mrj
1
@Rahul Chhetri 不确定我在哪里可以将其添加到仅调试配置中。我不知道应用程序组可以分配给哪个配置。请澄清一下。 - JeffB6688
3
@JeffB6688 好的,一开始我也有点困惑。但是当我打出我的原始想法时,我发现了这个。如果你点击你的项目,在那里有“General, Signing & capabilities”等选项。选择Signing & Capabilities,在下面有“All, Debug, Release”。这就是Rahul所说的“将应用程序组添加到调试和发布中”的意思。 - Mark
能力和权限在debugrelease之间有时由Xcode以不同方式管理,所以这可能发生在我们很多人身上。 - benc

1

0
浪费了很多时间,问题在于只为发布配置添加应用组,并且在调试配置的测试期间无法访问应用扩展和容器应用之间的共享数据。所以,请确保在发布和调试的两个配置中都添加应用组。

enter image description here


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