简体   繁体   中英

iCloud and Core Data pre-filled database

I have an app with a pre-filled .sqlite file that is copied into the user's Documents directory when the app is first opened. This file is 12.9MB. Twice now, my app has been rejected since changing target to iOS5 with this rejection note:

Binary Rejected Apr 24, 2012 10:12 AM

Reasons for Rejection:

2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected

Apr 24, 2012 10:12 AM. From Apple.

2.23

We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.

In particular, we found that on content download, your app stores 12.81 MB. To check how much data your app is storing:

  • Install and launch your app
  • Go to Settings > iCloud > Storage & Backup > Manage Storage
  • If necessary, tap "Show all apps"
  • Check your app's storage

The iOS Data Storage Guidelines indicate that only content that the user creates using your app, eg, documents, new files, edits, etc., may be stored in the /Documents directory - and backed up by iCloud.

Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.

Data that can be recreated but must persist for proper functioning of your app - or because customers expect it to be available for offline use - should be marked with the "do not back up" attribute. For NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCFURLIsExcludedFromBackupKey attribute.

For more information, please see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes?.

It is necessary to revise your app to meet the requirements of the iOS Data Storage Guidelines.

I have tried setting the "do not back up" attribute as recommended in the Data Storage Guidelines, but is was rejected again.

I do not use iCloud in my app, and Settings > iCloud > etc. shows no usage at all.

I cannot use the Caches or tmp directories as the database is modified by the user after creation.

I seem to be between a rock and a hard place here with Apple not allowing this kind of app to function at all.

Has anyone had this problem and managed to overcome it?

EDIT 17-5-12 I still haven't managed to get this app approved yet. Has anyone managed to do this?

EDIT 1-7-12 My app has just been rejected again for the same reason. I am at a loss as to what to do here, as surely it is a common use scenario.

EDIT 11-9-12 App now approved - please see my solution below. I hope it can help someone else.

OK, here is the solution I managed to get approved (finally!)

This is the code for setting the Skip Backup attribute - note that it is different for 5.0.1 and below and 5.1 and above.

#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;
    }
}

And here is my 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;
}

Note that I made the decision to just store in Caches and put up with purging for iOS 5.0 users.

This was approved by Apple this month.

Please don't copy and paste this code without reading and understanding it first - it may not be totally accurate or optimised, but I hope it can guide someone to a solution that helps them.

#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;
}

You said that you don't use iCloud. In that case, you should simply move your sqlite file to a directory with suffix .nosync . That should do it!

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.

I've been looking into this and have found this very interesting article on the subject : http://iphoneincubator.com/blog/data-management/local-file-storage-in-ios-5

I believe that you will continue to be rejected if you try to use the /Documents folder to store your DB file.

I would suggest you bite the bullet and use /Cache. The worst user case scenario would be that their device runs low on memory and the app gets cleaned removing the DB file. In this case when your app launches again it should copy over the bundled DB file into /Cache and the user would sync to go and grab the excess data from your remote server. That is assuming this is how your app works.

To get your app to move your DB file from /Documents to /Cache you can just tell NSFileManager to do this for you ...

#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);
    }

This will prevent your existing users from using their iCloud storage unnecessarily.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM