在应用组中实现应用间数据的通信和持久化

91

iOS 8昨天发布了一个涉及App Groups的新API。在此之前,共享数据和在应用程序之间进行通信有点混乱,我相信这正是App Groups旨在纠正的问题。

在我的应用程序中,我已经启用了App Groups并添加了一个新组,但我就是找不到任何关于如何使用它的文档。文档和API参考只说明如何添加组。

那么,App Groups的真正目的是什么?是否有任何文档介绍如何使用它?

6个回答

86

使用App Groups的另一个好处是可以共享NSUserDefaults数据库,这也适用于应用程序扩展(通知中心小部件、自定义键盘等)。

在应用程序组中的所有应用程序中像这样初始化您的NSUserDefaults对象,它们将共享数据库:

Objective-C:

[[NSUserDefaults alloc] initWithSuiteName:@"<group identifier>"];

Swift:


Swift 是一种流行的编程语言,由 Apple 公司在 2014 年推出。它旨在为 iOS、macOS 和其他 Apple 平台提供一种更快、更安全和更易于使用的编程语言。
NSUserDefaults(suiteName: "<group identifier>")

请注意,每个应用程序的[NSUserDefaults standardUserDefaults]数据库中的所有内容都不会转移到此数据库中。

文档(截至Beta 3)也给出了正确的示例。

并且不要忘记同步数据库:

[yourDefaults synchronize];

2
仅在模拟器上运行(XCode 6 beta 5,iOS 8 beta 5) - iOS Dev
8
注意:如果您正在使用iOS 8扩展(例如键盘),则需要确保您的扩展在Info.plist文件中具有RequestsOpenAccess=YES。 - Kiran Panesar
1
我执行了RequestsOpenAccess=YES并按照说明进行操作,但在设备上无法正常工作,而在模拟器上可以正常工作,这是设备特定的问题吗? - MAC
我也遇到了同样的问题,我在我的应用程序中集成了共享扩展,在模拟器上可以工作,但在真实设备上无法工作。我已经添加了RequestOpenAccess = YES,但它没有起作用。我的问题是:http://stackoverflow.com/questions/30489894/share-voice-memo-files-in-ios-application - Ravi Ojha
3
@MikeRichards,不太确定,但如果我没记错的话,应用程序组是以团队ID为前缀的。 - Andrew
显示剩余5条评论

79

在多个应用程序之间共享NSUserDefaults数据

为了在一个应用程序和扩展或两个应用程序之间拥有共享的默认值,您需要按照以下步骤在设置中添加一个App Group:

  1. 在项目导航器中单击*.xcodeproj文件(应该位于顶部)。
  2. 在Project和Targets旁边,找到Project和Targets。 在targets下单击主要目标(应该是Targets下的第一件事)。
  3. 向上滚动,单击Capabilities选项卡。
  4. 在App Groups部分,单击右侧的开关以打开App Groups。
  5. 单击+按钮,然后添加名为group.com.company.myApp 的 App Group。
  6. 在其他应用程序的相同位置进行操作,此组现在应该可供选择。 对将使用此共享数据的每个应用程序启用此组。

注意:如果您转到Apple Developer Portal(显示所有证书、标识符、设备和预配配置文件的Apple网站)并转到Identifiers > App Groups,则应看到此新的App Group。

存储数据:

var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")!
userDefaults.setObject("user12345", forKey: "userId")
userDefaults.synchronize()

获取数据的方法:

var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")
if let testUserId = userDefaults?.objectForKey("userId") as? String {
  print("User Id: \(testUserId)")
}

2
谢谢!我花了一些时间在文档中搜索,才弄清楚如何做到这一点,我想其他人可能会欣赏我所采取的步骤。 - TenaciousJay
感谢详细的描述!我们需要在苹果开发者门户网站(显示您的证书、标识符、设备和预配文件的苹果网站)创建任何证书来启用此组吗?就像推送通知一样。 - Arbaz Shaikh
当您执行第5步时,它应该将App Group添加到Apple开发者门户网站,您不需要直接进入开发者门户网站进行添加。在完成第5步后,您可以转到门户网站并查看它是否已为您创建。 - TenaciousJay
@TenaciousJay 绝妙的回答。 只想补充一下如果你想从以下函数启动应用程序:func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool - Taimur Ajmal
上传应用程序还有其他的步骤需要遵循吗?我正在原生iOS和Unity应用程序之间解析数据。 - Krunal Nagvadia
显示剩余3条评论

37

根据我对现有文档的理解,应用程序组主要面向扩展,更具体地说是小部件。小部件是其自己的应用程序包,与您的应用程序共存。由于它们是单独的应用程序,因此具有自己的沙箱,您需要使用应用程序组来共享文件。

经过一些头文件的搜索,我认为我找到了所需的API,但实际上这是作为iOS 7的一部分加入的。

NSFileManager有一个方法containerURLForSecurityApplicationGroupIdentifier:,您可以通过该方法传递在打开应用程序组时创建的标识符:

NSURL *containerURL = [[NSFileManager defaultManager] 
           containerURLForSecurityApplicationGroupIdentifier:@"group.com.company.app"];

谢谢,我相信你对可扩展性的看法是正确的。但我对iOS 7的这种方法有些怀疑,它对我来说似乎相当静态,而iOS 8则引入了应用程序之间的真正交互和通信。 - streem
@Justafinger 这个iOS 7的方法只用于创建URL,以便在共享应用程序之间读写数据。从我目前看来,这种特定的方法只对小部件有用,而不适用于其他扩展。 - Wayne Hartman
我相信苹果在iOS 8中希望使这个过程更加简单,而不必通过编程方式创建容器并共享自定义文件。文档指出,您应该能够使用NSUserDefault来共享数据。我想我可能漏掉了什么,因为这对我来说不起作用。 - streem
@Justafinger 我也没能够让 NSUserDefaults 正常工作,我认为这是 Beta 1 版本的 bug。 - Wayne Hartman
@WayneHartman NSUserDefaults数据库有一个解决方法。请查看我的回答。 - Andrew

6
今天我遇到的一个重要陷阱如下:
在许多项目中,我看到了一个单一的应用程序目标,并为该目标的每个配置设置了不同的包标识符。这里事情变得混乱。开发人员的意图是为调试配置创建一个调试应用程序和为发布目标创建一个生产应用程序。
如果您这样做,当它们被设置为这样时,两个应用程序将共享相同的NSUserDefaults。
var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")
userDefaults!.setObject("user12345", forKey: "userId")
userDefaults!.synchronize()

这会在很多地方引起问题:

  1. 假设你在用户看完特殊的应用介绍页面后,将一个键设置为YES。其他应用程序现在也会读取YES并且不显示介绍页面。
  2. 有些应用程序还将oAuth令牌存储在其用户默认设置中。无论如何...根据实现方式,应用程序将识别到有令牌,并使用错误的令牌开始检索数据。这样做的机会很大,会导致出现奇怪的错误。

一般情况下,解决此问题的方法是使用当前构建的默认键前缀。您可以通过为配置设置不同的包标识符来轻松检测运行时配置。然后只需从NSBundle.mainBundle()中读取包标识符即可。如果您具有相同的包标识符,则需要设置不同的预处理器宏,例如

#ifdef DEBUG
  NSString* configuration = @"debug";
#elif RELEASE
  NSString* configuration = @"release";
#endif

在 Swift 中,它看起来几乎相同:
#if DEBUG
  let configuration = "debug"
#elseif RELEASE
  let configuration = "release"
#endif

9
你的问题是因为你将所有数据混合在一个共享的UserDefaults中。对于不需要共享的数据,你应该使用NSUserDefaults.standardUserDefaults() - Daniel

4

iOS App Group

App Group 允许您在同一开发团队(账户)的不同进程(应用程序、扩展等)之间共享数据(UserDefaults、文件、CoreData(管理模型图)、POSIX锁)。它创建了一个具有 id共享容器,应以 group. 开头保存/缓存数据,您可以通过URL和IPC访问。

要使用 UserDefaults 使用 App Group

  1. 为将要从中获取访问权限的所有目标(应用程序、扩展等)添加相同 id 的 App Group 能力 enter image description here

创建后,您可以在 Apple Developer 上进行检查。证书、ID 和配置文件 -> 所有组

enter image description here

  1. 从Application1编写
let defaults = UserDefaults(suiteName: "group.goforit")
defaults?.setValue("Hello World!", forKey: "key1")
  1. 从Application2读取
let defaults = UserDefaults(suiteName: "group.goforit")
let result = defaults?.value(forKey: "key1") //Hello World!

共享容器根位置单一URL。
let rootURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.goforit")

//file:///Users/alex/Library/Developer/CoreSimulator/Devices/1AF41817-FE2E-485A-A592-12C39C0B0141/data/Containers/Shared/AppGroup/DC14D43F-2C2C-4771-83BE-64A9F54BD2E1/

[iOS应用扩展]


如何使用App Groups以及User Defaults的简单但非常有用的解释@yoAlex5 - Raja Saad

2

存储

let shared: NSUserDefaults = NSUserDefaults(suiteName: "group.abcapp")!
shared.setObject("abc.png", forKey: "favEmoji")
shared.synchronize()

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