18

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

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

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

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

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

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

即使数据库中没有样本数据,任何由数据库支持的应用程序在启动时仍然必须生成一个空数据库——即使它唯一拥有的是应用程序的数据库模式。

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

在这一点上我有什么选择?这是我能看到的:

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

  2. 我可以将文件的备份属性设置为 NO,然后仅在用户进行第一次更改时将其切换为 YES 吗?这在技术上可以吗,对苹果来说还可以吗?

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

  4. 哭。

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

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

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

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

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

4

8 回答 8

5

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将由您的数据库支持。

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

于 2012-07-09T15:11:07.710 回答
4

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

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

于 2012-07-11T11:11:59.073 回答
3

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

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

不幸的是,您必须针对 iOS 5。

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

于 2012-07-07T12:32:14.187 回答
0

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

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

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

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

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

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

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

于 2012-07-09T14:30:45.187 回答
0

只需添加以下属性,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];
    ...
于 2012-07-09T20:41:04.107 回答
0

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

于 2012-07-10T13:37:20.310 回答
0

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

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

但是,具体来说,在应用程序首次运行时将其启动。您是在询问用户是否需要在文档中包含样本数据以及数据的大小,因此他们是创建文档的人。还提到可以随时删除或替换样本数据。

这应该会让你通过审查。

于 2012-07-10T15:46:27.033 回答
-1

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

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

于 2012-07-09T14:40:37.347 回答