问题 iCloud和Core Data预填充数据库


我有一个带有预先填充的.sqlite文件的应用程序,该文件在首次打开应用程序时被复制到用户的Documents目录中。这个文件是12.9MB。现在两次,我的应用程序已被拒绝,因为使用此拒绝注释将目标更改为iOS5:

二进制被拒绝2012年4月24日上午10:12

拒绝的理由:

2.23应用必须遵循iOS数据存储指南,否则将被拒绝

2012年4月24日上午10:12来自Apple。

2.23

我们发现您的应用不符合iOS数据存储指南,这是根据App Store审核指南所要求的。

特别是,我们发现在内容下载时,您的应用程序存储了12.81 MB。要检查您的应用存储的数据量:

  • 安装并启动您的应用
  • 转至设置> iCloud>存储和备份>管理存储
  • 如有必要,请点按“显示所有应用”
  • 检查您应用的存储空间

iOS数据存储指南指出,只有用户使用您的应用创建的内容(例如,文档,新文件,编辑等)可以存储在/ Documents目录中 - 并由iCloud备份。

应用程序使用的临时文件只应存储在/ tmp目录中;请记住在用户退出应用程序时删除存储在此位置的文件。

可以重新创建但必须保持应用程序正常运行的数据 - 或者因为客户希望它可供脱机使用 - 应标记为“不备份”属性。对于NSURL对象,请添加NSURLIsExcludedFromBackupKey属性以防止备份相应的文件。对于CFURLRef对象,请使用相应的kCFURLIsExcludedFromBackupKey属性。

有关详细信息,请参阅技术问答1719:如何防止文件备份到iCloud和iTunes?。

有必要修改您的应用程序以满足iOS数据存储指南的要求。

我已尝试按照“数据存储指南”中的建议设置“不备份”属性,但又被拒绝了。

我不在我的应用程序中使用iCloud,而设置> iCloud>等根本没有显示任何用法。

我不能用 Caches 要么 tmp 用户在创建后修改目录作为数据库。

我似乎是在摇滚和硬地之间与Apple不允许这种应用程序运行。

有没有人遇到过这个问题并设法克服它?

编辑17-5-12 我还没有设法让这个应用程序获得批准。有没有人设法做到这一点?

编辑1-7-12 出于同样的原因,我的应用程序再次遭到拒绝。我不知道该怎么做,当然这是一个常见的使用场景。

编辑11-9-12 应用程序现已批准 - 请参阅下面的解决方案。我希望它可以帮助别人。


5146
2018-04-25 09:04


起源

我也面临这个问题。我担心你再次遭到拒绝。您是否尝试在整个文件夹中设置属性?我担心Apple只是拒绝不是核心数据的数据模型。如果您有任何其他信息,请发布您的发现。 - Lee Probert
我被拒绝了3次。我没有尝试将属性设置为整个文档文件夹,因为它不应该有所作为。您的应用是否因此原因被拒绝了? - colincameron
是。您是否考虑过将数据库文件保存到/ Library?也许只是因为它位于/ Documents文件夹中,这仅适用于用户生成的内容。应该可以编写自己的/ Database文件夹。 - Lee Probert
我可以允许@LeeProbert将/ Library文件夹用于用户生成的内容吗?问题是一个文件是两者的混合。 - colincameron
我注意到Google Analytics SDK for iOS已更新,其SQL lite文件正从/ Documents移至/ Library。我刚刚用相同的更改重新提交了我的应用。会告诉你这件事的进展的。 - Lee Probert


答案:


好的,这是我设法获得批准的解决方案(最后!)

这是用于设置“跳过备份”属性的代码 - 请注意,对于5.0.1及更低版本以及5.1及更高版本,它是不同的。

#include <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    if (&NSURLIsExcludedFromBackupKey == nil) { // iOS <= 5.0.1
        const char* filePath = [[URL path] fileSystemRepresentation];

        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;

        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        return result == 0;
    } else { // iOS >= 5.1
        NSError *error = nil;
        [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
        return error == nil;
    }
}

这是我的 persistentStoreCoordinator

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"store.sqlite"];

    NSError *error;

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent:@"store.sqlite"];

    // For iOS 5.0 - store in Caches and just put up with purging
    // Users should be on at least 5.0.1 anyway
    if ([[[UIDevice currentDevice] systemVersion] isEqualToString:@"5.0"]) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *cacheDirectory = [paths objectAtIndex:0];
        NSString *oldStorePath = [storePath copy];
        storePath = [cacheDirectory stringByAppendingPathComponent:@"store.sqlite"];
        storeURL = [NSURL URLWithString:storePath];

        // Copy existing file
        if ([fileManager fileExistsAtPath:oldStorePath]) {
            [fileManager copyItemAtPath:oldStorePath toPath:storePath error:NULL];
            [fileManager removeItemAtPath:oldStorePath error:NULL];
        }
    }
    // END iOS 5.0

    if (![fileManager fileExistsAtPath:storePath]) {
        // File doesn't exist - copy it over
        NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"store" ofType:@"sqlite"];
        if (defaultStorePath) {
            [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
        }
    }

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    [self addSkipBackupAttributeToItemAtURL:storeURL];

    return __persistentStoreCoordinator;
}

请注意,我决定只是存储 Caches 并忍受清除iOS 5.0用户。

本月苹果批准了这一点。

请不要复制并粘贴此代码而不首先阅读和理解它 - 它可能不完全准确或优化,但我希望它可以引导某人找到帮助他们的解决方案。


9
2017-09-11 15:30





#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#include <sys/xattr.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

//Put this in your method

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSURL *pathURL= [NSURL fileURLWithPath:documentsDirectory];

    iOS5 = NO;
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0.1")) {
        iOS5 = YES;
    }

    // Set do not backup attribute to whole folder
    if (iOS5) {
        BOOL success = [self addSkipBackupAttributeToItemAtURL:pathURL];
        if (success) 
            NSLog(@"Marked %@", pathURL);
        else
            NSLog(@"Can't marked %@", pathURL);
    }
}

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    const char* filePath = [[URL path] fileSystemRepresentation];
    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;
    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}

4
2018-04-25 11:40



谢谢你的答案 - BTW缩进代码有4个空格来正确格式化。你是说我应该将属性设置为整个文档文件夹?这是我正在使用的确切方法,除了我将它应用于该文件。 - colincameron
他说他试过“不要备份”的旗帜。 - Stephen Darlington
此外..此代码不支持5.1及以上。您只应该使用该方法在5.0.1中设置属性。 - badweasel


你说你不使用iCloud。在这种情况下,你应该简单地移动你的 sqlite 文件到带后缀的目录 .nosync。应该这样做!

NSString *dataFileName = @"my.sqlite";
NSString *dataFileDirectoryName = @"Data.nosync";
NSString *documentsDirectoryPath = [self applicationDocumentsDirectory];
NSFileManager *fileManager = [NSFileManager defaultManager];

if ([fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:dataFileDirectoryName]] == NO) {
    NSError *fileSystemError;
    [fileManager createDirectoryAtPath:[documentsDirectoryPath stringByAppendingPathComponent:dataFileDirectoryName]
           withIntermediateDirectories:YES
    attributes:nil
         error:&fileSystemError];

    if (fileSystemError != nil) {
        NSLog(@"Error creating database directory %@", fileSystemError);
    }
}

NSString *dataFilePath = [[documentsDirectoryPath
                           stringByAppendingPathComponent:dataFileDirectoryName]
                          stringByAppendingPathComponent:dataFileName];

// Move your file at dataFilePath location!

HTH。


1
2017-08-02 07:13



谢谢你的建议。你能指点我的文件吗? .nosync后缀?我之前没有听过那个。 - colincameron
使用iCloud发行说明“使用核心数据”中的“库类型应用程序指南”部分。 developer.apple.com/library/ios/#releasenotes/DataManagement/... - Mustafa
这听起来像你对我的回答。 - badweasel


我一直在研究这个问题并找到了关于这个主题的非常有趣的文章: http://iphoneincubator.com/blog/data-management/local-file-storage-in-ios-5

我相信如果您尝试使用/ Documents文件夹存储数据库文件,将继续被拒绝。

我建议你咬紧牙关并使用/ Cache。最糟糕的用户案例场景是他们的设备内存不足,应用程序清理后删除了DB文件。在这种情况下,当您的应用再次启动时,它应该将捆绑的DB文件复制到/ Cache中,并且用户将同步以从远程服务器获取多余的数据。这假设这是您的应用程序的工作方式。

要让您的应用程序将数据库文件从/ Documents移动到/ Cache,您可以告诉NSFileManager为您执行此操作...

#define FILE_MANAGER     [NSFileManager defaultManager]
#define DOCUMENTS_PATH   [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
#define CACHES_PATH      [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
#define DB_DOCS_PATH     [DOCUMENTS_PATH stringByAppendingPathComponent: @"database.db"]
#define DB_PATH          [CACHES_PATH stringByAppendingPathComponent: @"database.db"]

if([FILE_MANAGER moveItemAtPath: DB_DOCS_PATH toPath:DB_PATH error:&error])
    {
        NSLog(@"SUCCESSFULLY MOVED %@ to %@",DB_DOCS_PATH,DB_PATH);
    }

这将阻止现有用户不必要地使用其iCloud存储。


0
2017-07-02 14:06



我注意到Google分析iOS SDK还将其sql文件复制到/ Documents文件夹,所以我看看是否有更新。有 ... feeds.feedburner.com/ga-dev-changelog-collection-ios  它们现在保存到/ Library而不是保存在/ Caches文件夹中,而只保存在根目录中。 - Lee Probert
不幸的是,丢失数据不是我的应用程序的选项。该应用程序附带了许多乐谱/和弦表,用户可以创建这些表的播放列表以提高性能。这些播放列表可以保存并稍后调用,因此我不希望删除数据。 - colincameron
是他们在设备上保存的所有内容,还是您同步到远程服务器?如果你不是那么我会考虑它。查看Parse.com作为选项。然后,您可以安全地清理缓存并使其从远程服务器重新同步。 - Lee Probert
保存到/ Library文件夹是否需要标记为不备份? - Eric
我相信iTunes会备份/ Library文件夹,但不确定这是否与iCloud备份/ Documents文件夹和使用存储空间相同。 - Lee Probert