Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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, e.g., 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.

like image 662
colincameron Avatar asked Apr 25 '12 09:04

colincameron


2 Answers

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.

like image 135
colincameron Avatar answered Oct 18 '22 05:10

colincameron


#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;
}
like image 4
Developer Avatar answered Oct 18 '22 06:10

Developer