Предварительная база данных iCloud и Core Data

У меня есть приложение с предварительно заполненным файлом .sqlite, который копируется в каталог "Документы пользователя" при первом открытии приложения. Этот файл составляет 12,9 МБ. Дважды теперь мое приложение было отклонено с момента изменения цели на iOS5 с этой запиской об отказе:

Binary Rejected Apr 24, 2012 10:12 AM

Причины отторжения:

2.23 Приложения должны следовать рекомендациям по хранению данных iOS или отклоняться

24 апреля 2012 г. 10:12. От Apple.

2,23

Мы обнаружили, что ваше приложение не соответствует Руководствам по хранению данных iOS, которое требуется в Руководстве по обзору приложений для магазина.

В частности, мы обнаружили, что при загрузке контента ваше приложение хранит 12,81 МБ. Чтобы проверить, сколько данных хранится ваше приложение:

  • Установка и запуск приложения
  • Перейдите в Настройки > iCloud > Хранение и резервное копирование > Управление хранилищем
  • При необходимости коснитесь "Показать все приложения"
  • Проверьте хранилище приложений

Руководства по хранению данных iOS указывают, что только содержимое, созданное пользователем с помощью вашего приложения, например, документы, новые файлы, изменения и т.д., может храниться в каталоге /Documents и подкреплено iCloud.

Временные файлы, используемые вашим приложением, должны храниться только в каталоге /tmp; не забудьте удалить файлы, хранящиеся в этом месте, когда пользователь выходит из приложения.

Данные, которые могут быть воссозданы, но должны сохраняться для правильной работы вашего приложения, или потому, что клиенты ожидают его доступности для использования в автономном режиме, должны быть отмечены атрибутом "не создавать резервные копии". Для объектов NSURL добавьте атрибут NSURLIsExcludedFromBackupKey, чтобы предотвратить резервное копирование соответствующего файла. Для объектов CFURLRef используйте соответствующий атрибут kCFURLIsExcludedFromBackupKey.

Для получения дополнительной информации см. Техническое Q & A 1719: Как предотвратить резервное копирование файлов в iCloud и iTunes?.

Необходимо изменить ваше приложение, чтобы оно соответствовало требованиям Руководства по хранению данных iOS.

Я попытался установить атрибут "не создавать резервные копии", как рекомендовано в Руководстве по хранению данных, но снова был отклонен.

Я не использую iCloud в своем приложении, а Settings > iCloud > и т.д. вообще не использует.

Я не могу использовать каталоги Caches или tmp, поскольку база данных будет изменена пользователем после создания.

Кажется, я между рок-н-роллом и жестким местом здесь, когда Apple не разрешает такое приложение вообще функционировать.

У кого-нибудь была эта проблема и удалось ее преодолеть?

РЕДАКТИРОВАТЬ 17-5-12 Я до сих пор не получил одобрение этого приложения. Кто-нибудь смог это сделать?

РЕДАКТИРОВАТЬ 1-7-12 Мое приложение только что было отклонено снова по той же причине. Я не понимаю, что здесь делать, так как это сценарий общего использования.

РЕДАКТИРОВАТЬ 11-9-12 Теперь приложение одобрено - см. Мое решение ниже. Надеюсь, это поможет кому-то другому.

Ответ 1

ОК, вот решение, которое мне удалось получить (наконец!)

Это код для установки атрибута Skip Backup - обратите внимание, что он отличается от 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.

Это было одобрено Apple в этом месяце.

Пожалуйста, не копируйте и не вставляете этот код, не читая и не понимая его сначала - он может быть не совсем точным или оптимизированным, но я надеюсь, что он может помочь кому-то найти решение, которое им поможет.

Ответ 2

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

Ответ 3

Вы сказали, что вы не используете 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!

НТН.

Ответ 4

Я изучал это и нашел эту очень интересную статью на эту тему: http://iphoneincubator.com/blog/data-management/local-file-storage-in-ios-5

Я считаю, что вы будете продолжать отклоняться, если попытаетесь использовать папку /Documents для хранения вашего файла DB.

Я бы посоветовал вам кусать пулю и использовать /Cache. В худшем случае сценарий для пользователей будет заключаться в том, что их устройство мало работает в памяти, и приложение очищается, удаляя файл DB. В этом случае, когда ваше приложение запускается снова, оно должно скопировать через связанный файл DB в /Cache, и пользователь будет синхронизироваться, чтобы перейти и захватить лишние данные с вашего удаленного сервера. Это предполагает, что это работает ваше приложение.

Чтобы ваше приложение могло перемещать ваш файл DB из /Documents to/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 без необходимости.