iPhone 应用因 iCloud 数据存储指南而被拒绝

Posted

技术标签:

【中文标题】iPhone 应用因 iCloud 数据存储指南而被拒绝【英文标题】:iPhone App Rejected because of iCloud Data Storage Guidelines 【发布时间】:2012-07-07 12:24:17 【问题描述】:

由于涉嫌违反 iCloud 数据存储指南,我最近拒绝对我的一个应用进行重大错误修复更新。

以下是我的应用程序存储数据的方式(自 2009 年我的应用程序的第一个版本获得批准以来,这一直不是问题):

    在启动时,它会将“starter”SQLite3 数据库从应用程序包复制到 Documents 文件夹。 数据库包含基本架构和一些示例数据,因此用户可以了解如何使用该应用程序。它很小 - 不到 3MB。 然后,用户未来的工作将仅保存在此数据库文件中。他们可能会删除或保留样本,可能会添加大量自己的数据,但该数据库文件将始终存在。

今年早些时候的一次更新因为同样的原因被拒绝了,但是当我给他们上面的解释时,应用程序状态从“拒绝”变为“审查中”再到“正在处理 App Store”。他们没有给我任何解释,所以我认为这只是审稿人的误解。

这一次,审阅者对我的解释作出了回应,只是说非用户生成的数据不应存储在 iCloud 中,我的更新仍处于“拒绝”状态。

但我不明白我应该在这里做什么。因为所有用户的工作都保存在数据库中,所以我不能将其从 iCloud 备份中排除或将其存储在缓存文件夹中。此外,我无法真正将“用户生成”数据与“非用户生成”数据完全分开,因为该应用程序使用同一个数据库文件。初始的、非用户生成的数据将很快被用户自己的数据替换,但数据库的文件名和目录位置将保持不变。

即使数据库中没有示例数据,任何由数据库支持的应用程序在启动时仍必须生成一个空数据库 - 即使它唯一保存的是应用程序的数据库架构。

这一定是一个很常见的问题,但不幸的是,我不能只关闭备份——用户在他们存储在我的应用程序中的数据上投入了大量工作,而 iCloud 备份对他们来说非常重要。

此时我有什么选择?这是我能看到的:

    再次联系 Apple 并尝试解释发生了什么。

    我能否将文件的备份属性设置为 NO,然后仅在用户进行第一次更改时才将其切换为 YES?这在技术上是否可行,对 Apple 是否可行?

    从数据库中删除我的示例数据。这对可用性真的很不利,并且会增加我的支持负担,但如果它能让我的更新获得批准,我愿意这样做。但是,我仍然需要在启动时创建一个存根数据库来保存空的数据库架构,所以我不确定这是否会对审批流程产生影响。

    哭泣。

有人有什么建议吗?我不得不想象有很多其他应用程序使用数据库的方式与我的方式相同,但没有仅禁用备份的选项。

同样令人沮丧的是,我所做的任何更改都需要另一轮测试和应用审查,这将使我的关键更新延迟 2 到 3 周。 :/

更新:可能还有另一种选择:我可以简单地将文件保存在Library/ 而不是Documents/,因为他们的问题似乎与使用 Documents 文件夹有关吗?如果文件存储在Library/,是否会备份文件?

更新 2:我发现最令人困惑的是任何数据库支持的应用程序(即使它使用 Core Data,我假设)都必须创建一个至少包含应用程序架构的数据库文件.问题只是我的数据库太大了吗?因为我看不出任何支持数据库的应用程序如何避免在启动时创建数据库。

更新 3:我使用的是自定义 SQLite 交互层 - 而不是 Core Data。此外,示例数据包含初始图像,用户在开始使用应用程序时可能最终会删除这些图像。

【问题讨论】:

只是想让你知道你可以使用developer.apple.com/appstore/contact/appreviewteam/index.html 我们最近用它来修复一个严重的错误,我们的应用会在 1 天内得到审核!! “审阅者回应了我的解释,只是说非用户生成的数据不应该存储在 iCloud 中”我们的应用出于同样的原因被拒绝了一次。内容不是应用程序生成的,而是可下载的,我们将其从文档移动到缓存文件夹 我可能会选择选项 2。 @MSK,不幸的是,整个应用程序设计为使用一个 SQLite 数据库 - 将示例移动到 Caches 文件夹中需要大量的特殊情况和重写才能让应用程序同时读取缓存中的示例,然后将更改保存到文档... 3MB 的示例数据似乎很多,您是否在数据库中存储了任何图像? 【参考方案1】:

3MB 似乎是存储在数据库中的大量样本数据。如果您提取图像并将对图像的引用存储在数据库中,那么您应该能够大大降低这种使用量。然后,您可以将数据库的图像获取器代码更改为以下内容:

- (UIImage *)image

    NSString *imageName = self.imageName;
    UIImage *image = [UIImage imageNamed:imageName];
    if (!image)
    
        NSString *imageLibraryPath = ...;
        image = [UIImage imageWithPath:[imageLibraryPath stringByAddingPathComponent:imageName]];
    
    return image;


- (void)setImage:(UIImage *)image

    [self setImageData:UIImagePNGRepresentation(image)];


- (void)setImageData:(NSData *)imageData

    NSString *imageLibraryPath = ...;
    NSString *fileName = self.uniqueId; //or something else, UUID maybe?
    NSString *filePath = [imageLibraryPath stringByAddingPathComponent:imageName];
    [imageData writeToFile:filePath atomically:YES]; // maybe dispatch_async this into the background?
    self.fileName = fileName;

self.fileName 将由您的数据库支持。

通过以这种方式减少数据存储,您应该能够获得批准,因为您不会在手机上存储相对大量的数据。图片也可以通过这种方式放在 app bundle 中,根本不需要复制。

【讨论】:

这看起来很有希望。与在用户交互过程中尝试从只读应用程序包数据库切换到读写文档数据库相比,这似乎是一个不那么具有侵入性的更改。但是,即使是一个小型数据库也不会违反指导方针,让我回到我开始的地方吗? 刚刚看到您对原始问题的评论。我会尝试询问我的审稿人这是否有帮助。【参考方案2】:

我有一个非常愚蠢的想法。但也许它是有效的。

您可以在启动时显示一个弹出窗口,询问“您希望我创建一些演示数据”。当用户点击“是”时,其用户生成数据。

【讨论】:

这实际上是一个非常聪明的想法 (IMO)。 App Store 人们会看穿它和/或允许它是更大的问题。【参考方案3】:

一种解决方法是从包中读取 SQL 文件,直到用户尝试编辑某些内容。然后复制过来。由于文件很小,它甚至不需要微调器。

我遇到了这个确切的问题 最近的应用。在我们的例子中,我们使用核心数据并在启动时复制 SQL 文件。

不幸的是,您必须以 ios 5 为目标。

https://developer.apple.com/library/ios/qa/qa1719/_index.html

【讨论】:

谢谢 - 这是另一种选择。不幸的是,它可能需要大量的重构和测试,因为该应用程序当前编写为仅在启动时打开一个数据库并继续永远使用它。在中间交换它会对应用程序产生很大影响,并且在我什至可以重新提交应用程序之前需要进行大量测试。为什么您必须针对 iOS 5 来执行此操作? 目录位仅适用于 5.0.1。如果位失败,我没有将其移动到缓存中,而是决定如果审查团队要坚持测试它,这是帮助确保接受的唯一可靠方法。 这是个好主意。在写入数据库的代码中,只需检查它是否存在于 Documents 中,如果不存在则复制它。【参考方案4】:

只需添加以下属性,Apple就会让你通过(如果你没有使用

    #include <sys/xattr.h>
    ...
    - (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
    
    int result = -1;
    @try 
            const char* filePath = [[URL path] fileSystemRepresentation];
            const char* attrName = "com.apple.MobileBackup";
            u_int8_t attrValue = 1;
            result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
     @catch (NSException * e) 
            DebugLog(@"Exception: %@", e);
    
    return result == 0;
    

    - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
    ... 
    NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
    [self addSkipBackupAttributeToItemAtURL:storeUrl];
    ...

【讨论】:

a) 我没有使用 Core Data b) 此数据不能跳过 - 它确实需要备份。 这在 ios 5.0 上不可用。因此,即使您不希望将数据库备份到 icloud,它仍然无法在 ios 5.0 上运行【参考方案5】:

CoreData 和两个持久存储应该是要走的路。 但是对你来说,在第一次复制文件时设置 com.apple.MobileBackup 文件属性。在用户进行第一次修改后取消设置

【讨论】:

【参考方案6】:

作为最终解决方案,我建议使用 CoreData,将您的罐装数据作为额外的只读持久存储。

但是,与此同时,您需要一个简单、快速的解决方案。创建一个简单的屏幕,将样本数据加载到用户的数据库中。在“系统”或“工具”屏幕中放置一个链接,以便随时完成。

不过,特别是在应用程序首次运行时将其显示出来。您是在询问用户是否需要在文档中包含样本数据以及数据的大小,因此他们是创建文档的人。还提到可以随时删除或替换示例数据。

这应该会让你通过审查。

【讨论】:

【参考方案7】:

使用您的“私有”SQL 数据访问层

由于您使用的不是Core Data,而是自定义访问层,我只能建议不要将“初始种子”数据库放在iCloud文件夹中,而是编写一个在运行时生成它的自定义过程(例如,当您首先启动应用程序)。

最好的方法应该是将您的真实 SQL 文件放在通用应用程序包中,在 iCloud 共享文件夹之外。通过您的程序,您必须读取此文件的所有内容并在共享文件夹中重新创建一个克隆,以便此新文件将显示为用户生成的内容而不是捆绑的内容。这将需要一些无用开销,但我认为这是避免Apple拒绝您的应用所必需的。

如果可能的话,我建议您不要备份所有图像并将它们留在主包中,因为它们已经存在于每个下载的应用程序中,然后会无用地占用 iCloud 上的宝贵空间。尝试仅将绝对必要的内容放入 iCloud。

如果您计划同时使用 Core Data 和 iCloud。

首先,在 iCloud 中放置自定义 SQLite3 数据库文件是危险的,原因有两个,首先是如果您的应用在不同的平台上使用,您将永远无法正确处理不同数据之间的合并问题。设备并发。

我不能在这里给你一个完整的解决方案,因为它需要太多的时间和空间。但我建议你看看今年 Apple WWDC 的 Session 227 演讲。他们谈到一起使用 Core Data 和 iCloud,还解决了您现在面临的相同问题(即通过 iCloud 进行初始数据库播种和同步)。如果您通过 Apple 开发者门户访问 WWDC 页面,您将能够获得示例项目的完整副本。

【讨论】:

这不是核心数据——它是带有我自己的数据访问层的原始 SQLite3。我将数据库保存在 Documents 中的唯一原因是它可以通过 iCloud 备份进行备份。我没有尝试在设备之间同步。 即使你不使用 Core Data 而是自定义访问层,我想你也会觉得这个谈话很有趣。也许最好的方法不是将原始 db 文件复制到文档文件夹,而是在运行时在文档文件夹本身中读取并创建它。这就像一种解决方法。 顺便说一句,将 iCloud 用作备份存储并不是最好的方法。这不是它的使用方式。您的应用可以由同一用户在不同设备上使用(希望),因此我建议您寻找不同的方法。 当然。但是目前没有同步支持,我需要解决一个关键的错误修复。现在,我的用户依靠简单的 iCloud 备份机制在设备外备份他们的数据。 好吧,我相信唯一的解决方案是在运行时生成该 SQL 文件(通过从非 iCloud 文件夹读取并写入 iCloud 文件夹),通过“模拟”播种算法,而不是放置它在构建时直接在 iCloud 文档文件夹中。这应该可以解决Apple的问题。告诉我,我很感兴趣。【参考方案8】:

如果您使用的是 Core Data,您可以将示例数据保存在 app bundle 中。在与 docs 目录中的读/写存储相同的协调器上将其作为只读持久存储打开,Core Data 将确保保存时内容保持在正确的位置。 WWDC 2012 的“使用核心数据的最佳实践”视频对此进行了解释。

如果您直接使用 SQLite,这并不容易,但您可以实现自己的逻辑来做同样的事情。

【讨论】:

我直接使用 SQLite。我并不乐观地认为我能够在不引入任何问题的情况下从只读连接切换到读写连接。

以上是关于iPhone 应用因 iCloud 数据存储指南而被拒绝的主要内容,如果未能解决你的问题,请参考以下文章

iCloud 中保存的意外数据

iPhone:不需要icloud备份时将用户数据保存在哪里

由于 iOS 数据存储指南问题,我的应用拒绝了 Appstore

iPhone中的iCloud实现应用程序? [关闭]

iPhone应用程序因后台定位模式而被拒绝[关闭]

核心数据 - iCloud 行为