iOS中App Group无法传递数据的问题
iOS中App Group无法传递数据的问题

iOS中App Group无法传递数据的问题

在Xcode项目,为了iOS主应用和Watch应用之间实现数据传递,我创建了一个App Group(group.com.fangjunyu.piglet.watch)。

但是,在调用App Group的过程中,发现iOS应用和Watch应用之间无法使用App Group传递数据。

iOS端代码

在iOS端,我在主视图中调用savePiggyBankDataToAppGroup方法,这个方法会将Swift Data中的数据以字典数组的形式存储在App Group中。

// 将数据保存到 App Group UserDefaults
func savePiggyBankDataToAppGroup() {
    let piggyBankData: [[String: Any]] = allPiggyBank.map { bank in
        return [
            "name": bank.name,
            "icon": bank.icon,
            "amount": bank.amount,
            "targetAmount": bank.targetAmount,
            "isPrimary": bank.isPrimary
        ]
    }
    print("iOS 数据保存到 App Group UserDefaults中")
    print("piggyBankData:\(piggyBankData)")
    // 获取 App Group UserDefaults
    if let appGroupDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet.watch") {
        appGroupDefaults.set(piggyBankData, forKey: "piggyBanks")
        appGroupDefaults.synchronize() // 强制保存
        print("iOS 数据存储到 App Group 完成")
    } else {
        print("无法访问 App Group UserDefaults")
    }
}

func checkStoredData() {
    if let appGroupDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet.watch") {
        let storedData = appGroupDefaults.array(forKey: "piggyBanks")
        print("存储的数据:\(storedData ?? [])")
    }
}

在Xcode中进行测试,输出的内容为:

iOS 数据保存到 App Group UserDefaults中
piggyBankData:[["icon": "iphone.gen2", "name": "iPhone 16 pro max", "amount": 2000.0, "targetAmount": 9999.0, "isPrimary": true], ["icon": "laptopcomputer", "name": "Mac book pro", "amount": 3000.0, "targetAmount": 18888.0, "isPrimary": false]]
iOS 数据存储到 App Group 完成
应用移入后台,同步 Watch 数据
存储的数据:[{
    amount = 2000;
    icon = "iphone.gen2";
    isPrimary = 1;
    name = "iPhone 16 pro max";
    targetAmount = 9999;
}, {
    amount = 3000;
    icon = laptopcomputer;
    isPrimary = 0;
    name = "Mac book pro";
    targetAmount = 18888;
}]

输出的内容表示savePiggyBankDataToAppGroup方法执行成功,并且可以从App Group中读取存储的数据。

Watch端代码

在Watch端创建了一个类,这个类中含有一个加载App Group的方法,这个方法会从App Group中读取数据。

func loadPiggyBankDataFromAppGroup() {
    print("进入loadPiggyBankDataFromAppGroup 方法")
    if let appGroupDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet.watch") {
        
        let allDefaults = appGroupDefaults.dictionaryRepresentation()
         print("UserDefaults 中的所有内容:\(allDefaults)") // 打印所有内容
        
        // 尝试读取数据 
        if let piggyBankData = appGroupDefaults.array(forKey: "piggyBanks") as? [[String: Any]] {
            piggyBanks = piggyBankData.compactMap {
                if let name = $0["name"] as? String,
                   let icon = $0["icon"] as? String,
                   let amount = $0["amount"] as? Double,
                   let targetAmount = $0["targetAmount"] as? Double,
                   let isPrimary = $0["isPrimary"] as? Bool {
                    return PiggyBank(name: name, icon: icon, amount: amount, targetAmount: targetAmount, isPrimary: isPrimary)
                }
                return nil
            }
            
            if !piggyBanks.isEmpty {
                print("Watch 端接收数据成功,接收数据为:\(piggyBanks)")
            } else {
                print("Watch 端接收到的数据为空或无效。")
            }
        } else {
            print("Watch 端解码失败:未能读取piggyBanks数据")
        }
    } else {
        print("无法读取 App Group UserDefaults")
    }
}

但是,在Xcode测试的过程中,发现输出的内容为:

从UserDefaults中获取iOS数据
进入loadPiggyBankDataFromAppGroup 方法
Couldn't read values in CFPrefsPlistSource<0x175e3b40> (Domain: group.com.fangjunyu.piglet.watch, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
UserDefaults 中的所有内容:["AppleKeyboardsExpanded": 1, "AppleLanguagesSchemaVersion": 4000, "com.apple.content-rating.ExplicitBooksAllowed": 1, "INNextHeartbeatDate": 761823254.908258, "PKKeychainVersionKey": 8, "AKLastIDMSEnvironment": 0, "com.apple.content-rating.TVShowRating": 1000, "AppleLanguages": <__NSArrayM 0x175c3000>(
zh-Hans-CN,
en-CN
)
, "AppleKeyboards": <__NSArrayM 0x175c3120>(
ja_JP-Kana@sw=Kana;hw=Automatic,
en_US@sw=QWERTY;hw=Automatic,
zh_Hans-Pinyin@sw=Pinyin-Simplified;hw=Automatic,
zh_Hans-Pinyin@sw=Pinyin10-Simplified;hw=Automatic,
emoji@sw=Emoji
)
, "com.apple.system.SpellCheckAllowed": 1, "NSLanguages": <__NSSingleObjectArrayI 0x176e4120>(
en-001
)
, "com.apple.content-rating.AppRating": 1000, "AppleMeasurementUnits": Centimeters, "AKLastLocale": zh_CN, "NSInterfaceStyle": macintosh, "PKLogNotificationServiceResponsesKey": 0, "com.apple.content-rating.MovieRating": 1000, "AKLastEmailListRequestDateKey": 2025-02-21 20:12:09 +0000, "AppleLocale": zh_CN, "com.apple.content-rating.ExplicitMusicPodcastsAllowed": 1, "AppleTemperatureUnit": Celsius, "ApplePasscodeKeyboards": <__NSArrayM 0x175c31f0>(
zh_Hans-Pinyin@sw=Pinyin-Simplified;hw=Automatic,
en_US@sw=QWERTY;hw=Automatic,
emoji@sw=Emoji
)
, "AppleMetricUnits": 1]
Watch 端解码失败:未能读取piggyBanks数据

其中有两点,一点是在Watch应用中通过appGroupDefaults.dictionaryRepresentation()方法遍历App Group中的所有数据,并没有从iOS应用中保存的数据。第二点是,输出了一个报错:

Couldn't read values in CFPrefsPlistSource<0x175e3b40> (Domain: group.com.fangjunyu.piglet.watch, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

这个报错表示问题可能出现在权限、同步机制或配置不匹配等方面。

检查配置

检查Xcode项目中iOS端和Watch的Signing & Capabilities的内容:

iOS端和Watch端都配置了App Group,同时,因为我前几天刚新增了一个Widget(小组件),小组件也是通过App Group进行配置的。目前App Group的对应关系为:

检查Provisioning Profile和Signing Certificate字段都是一致的,因为本身也是我一个人在编写这个项目文件。

我通过查询网络,发现有以下几种可能的解决方案:

1、《NSUserDefaults errors in logs》讨论文章中表示,手机和主机设备重启可以解决这一问题。

2、《Failed to read values in CFPrefsPlistSource iOS 10》讨论文章中表示,在App Group前面添加团队ID可以解决这一问题。

因为重启这个方案比较大,重启后如果可以解决这一问题,但是无法进一步排查问题原因,因此我决定放在最后重启。

我首先是修改iOS和Watch之间的App Group名称,将当前的App Group:

group.com.fangjunyu.piglet.watch

改为团队ID前缀:

group.RRMSD743UQ.com.fangjunyu.piglet.watch

团队ID可以在App Store Connect的“编辑资料”中进行查看。

在iOS应用和Watch应用的App Groups中,新增团队ID前缀的App Group。

在Xcode代码中,也将iOS和Watch的App Group改为新增团队ID前缀的App Group。

运行后,输出的内容仍然是无法读取。

Couldn't read values in CFPrefsPlistSource<0x176939a0> (Domain: group.RRMSD743UQ.com.fangjunyu.piglet.watch, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

新的App Group

这里,我猜测跟App Group的字段过长有关,我尝试使用新的App Group:

group.RRMSD743UQ.pigletWatch

在测试的过程中,仍然无法读取,同时还发现了新的报错内容。

在iOS端使用检查数据方法后:

func checkStoredData() {
    print("group.RRMSD743UQ.pigletWatch")
    if let appGroupDefaults = UserDefaults(suiteName: "group.RRMSD743UQ.pigletWatch") {
        let storedData = appGroupDefaults.array(forKey: "piggyBanks")
        print("存储的数据:\(storedData ?? [])")
    }
}

Xcode输出的内容中,包含了存储的数据,但是仍然存在一个Couldn’t read values in CFPrefsPlistSource<0x30273a910> 的报错。

group.RRMSD743UQ.pigletWatch
存储的数据:[{
    amount = 3000;
    icon = laptopcomputer;
    isPrimary = 0;
    name = "Mac book pro";
    targetAmount = 18888;
}, {
    amount = 2000;
    icon = "iphone.gen2";
    isPrimary = 1;
    name = "iPhone 16 pro max";
    targetAmount = 9999;
}]
Couldn't read values in CFPrefsPlistSource<0x30273a910> (Domain: group.RRMSD743UQ.pigletWatch, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

因此,可以推断iOS 端在尝试读取 App Group 中的数据时出现了问题。

尝试之前的App Group

在修改多个App Group后,仍然无法解决问题。

// 无法传递的 App Group
group.com.fangjunyu.piglet.watch
// 第一次修改
group.RRMSD743UQ.com.fangjunyu.piglet.watch
// 第二次修改
group.RRMSD743UQ.pigletWatch

因此,尝试使用之前iOS主应用和Widget(小组件)的App Group,检查是否能够实现传递。

经过测试发现,即使使用Widget和iOS之间成功传递的App Group,iOS和Watch仍然无法使用App Group进行数据传递。

Couldn't read values in CFPrefsPlistSource<0x3001de910> (Domain: group.com.fangjunyu.piglet, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

因此,目前的推断是,问题跟App Group可能没有关系。

新的解决思路

现在的问题还有以下几种测试途径:

一种是iOS和Watch之间的传递方式有问题。既然Widget和iOS可以实现App Group,那么就复制Widget和iOS的传递方式到Watch和iOS之间,如果Watch和iOS可以通过Widget的方式传递,则表示可能是当前的传递方式有问题。

第二种是,App Group无法实现字典数组的数据传递,因此只能尝试其他的数据传递格式。

第三种就是重启Mac和Watch,从而检查是否因某些特殊原因,导致App Group无法使用。

Widget传递方式

iOS和Widget数据传递

iOS端通过调用saveWidgetData将数据存储到App Group中。

func saveWidgetData() {
    let userDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet")
    // 存储存钱罐数据
    userDefaults?.set(piggyBank[0].icon, forKey: "piggyBankIcon")
    userDefaults?.set(piggyBank[0].name, forKey: "piggyBankName")
    userDefaults?.set(piggyBank[0].amount, forKey: "piggyBankAmount")
    userDefaults?.set(piggyBank[0].targetAmount, forKey: "piggyBankTargetAmount")
    userDefaults?.set(LoopAnimation, forKey: "LoopAnimation")
    userDefaults?.set(BackgroundImage, forKey: "background")
}

因此,尝试修改iOS和Watch的传递方法为:

func savePiggyBankDataToAppGroup() {
    let userDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet")
    
    let piggyBankData: [[String: Any]] = allPiggyBank.map { bank in
        return [
            "name": bank.name,
            "icon": bank.icon,
            "amount": bank.amount,
            "targetAmount": bank.targetAmount,
            "isPrimary": bank.isPrimary
        ]
    }

    print("iOS 数据保存到 App Group UserDefaults中")
    print("piggyBankData:\(piggyBankData)")
    
    userDefaults?.set(piggyBankData, forKey: "piggyBanks")
}

修改后,在Watch端仍然无法获取到piggyBanks的信息。因此,跟数据传递的方法可能无关。

其他数据形式

既然iOS和Widget可以实现单值的数据传递,因此考虑先测试单个值的UserDefault是否可以连同iOS和Watch,如果证明成立。则侧面的反应了字典数组的形式可能不适应或者配置有误,导致的无法传递数据。

如果单值仍然无法在iOS和Watch之间传递,那么就只能重启设备,以检查问题能否解决。

修改iOS应用savePiggyBankDataToAppGroup的方法,添加一个Watch字段,用于测试iOS和Watch是否可以通过App Group连通。

func savePiggyBankDataToAppGroup() {
    let userDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet")
    
    let piggyBankData: [[String: Any]] = allPiggyBank.map { bank in
        return [
            "name": bank.name,
            "icon": bank.icon,
            "amount": bank.amount,
            "targetAmount": bank.targetAmount,
            "isPrimary": bank.isPrimary
        ]
    }

    print("iOS 数据保存到 App Group UserDefaults中")
    print("piggyBankData:\(piggyBankData)")
    
    userDefaults?.set(piggyBankData, forKey: "piggyBanks")
    
    // 存储存钱罐数据
    userDefaults?.set("测试Watch是否可以传递", forKey: "Watch")
}

	在iOS端保存后,Watch端读取时,显示为nil:

func loadPiggyBankDataFromAppGroup() {
    print("进入loadPiggyBankDataFromAppGroup 方法")
    print("group.com.fangjunyu.piglet")
    if let appGroupDefaults = UserDefaults(suiteName: "group.com.fangjunyu.piglet") {
        let watchData = appGroupDefaults.string(forKey: "Watch")
        print("测试Watch单值存储的数据:\(watchData)")
    }
}

这也就表示,单值也没有实现从iOS和Watch之间的传递数据,因此问题可能不在数据格式上,而是iOS和Watch之间无法使用App Group 通信。

但是,为什么iOS和Widget可以使用App Group,而iOS和Watch之间不可行呢?

重启设备

接下来是最后的验证,如果重启设备可以解决输出的报错,以及实现iOS和Watch之间的连接,那么就表示问题在于设备。

如果重启仍然无法解决这一问题,那么问题可能在于iOS和Watch直接无法通过App Group传递数据。

在重启Mac电脑后,发现之前的 Couldn’t read values in CFPrefsPlistSource<0x3001de910> 报错消失了。

但是测试Watch端时,仍然无法读取App Groups中的数据,还是输出:

Couldn't read values in CFPrefsPlistSource<0x175be9a0> (Domain: group.com.fangjunyu.piglet, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

然后尝试重启Watch端。

重新安装应用,发现Watch端仍然无法从App Groups中读取数据。

卸载iOS和Watch端应用后,重新安装应用,问题仍然无法解决。

总结

经过很长的测试,仍然未能解决这一问题。

有趣的是,我在搜索解决文章时,发现存在同样问题的文章《App group shared user defaults》。

在这个文章中有一个留言提到“这个现代系统将 watchOS 与 iOS 一起分发,但它们不能属于同一个应用程序“组”,无法共享“状态”或过去可以共享的任何数据位。”

另一篇stackoverflow文章《iPhone and Apple Watch not sharing App Group》的高赞回答表示:

随着 watchOS 2 和原生 watchOS 应用的引入,Apple Watch 应用不再仅仅是其 iOS 版本的扩展,因此它们不再共享 App Groups。

从 watchOS 2 开始,你无法再使用 App Groups 来共享数据。你必须使用 WatchConnectivity 框架来在 iOS 和 watchOS 应用之间发送数据。

因此,iOS和Watch应用之间已经无法通过App Group实现连接,这貌似已经是一个不可否认的事实。那么Watch应用应该如何实现iOS的同步呢?

一种是使用WatchConnectivity框架发送数据,但昨天刚写过一篇WatchConnectivity文章《iOS和Apple Watch数据交换的框架WatchConnectivit》,在使用WatchConnectivity过程中必须保持iOS和Watch应用在前台的状态,才能实现数据传递。

如果想要实现本地同步的情况,可能需要使用CloudKit,但是我的iOS应用是通过SwiftData同步到Cloud中的,Watch好像还不支持SwiftData,这样也无法实现从CloudKit同步数据。

因此,如果想要实现App Group类似的同步数据方法,将iOS的数据同步到Watch上,可能还需要仔细的研究一下。

参考文章

1、Failed to read values in CFPrefsPlistSource iOS 10:https://stackoverflow.com/questions/38275395/failed-to-read-values-in-cfprefsplistsource-ios-10/77923029#77923029

2、NSUserDefaults errors in logs:https://stackoverflow.com/questions/39876042/nsuserdefaults-errors-in-logs

3、App group shared user defaults:https://developer.apple.com/forums/thread/710966

4、iPhone and Apple Watch not sharing App Group:https://stackoverflow.com/questions/46140731/iphone-and-apple-watch-not-sharing-app-group

5、iOS和Apple Watch数据交换的框架WatchConnectivit:https://fangjunyu.com/2025/02/21/ios%e5%92%8capple-watch%e6%95%b0%e6%8d%ae%e4%ba%a4%e6%8d%a2%e7%9a%84%e6%a1%86%e6%9e%b6watchconnectivit/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注