Основные данные "База данных выглядит коррумпированной" - что вызывает эту ошибку?

Я стучу головой о стену здесь, я использую Core Data для SQLLite DB, и я могу успешно сохранить базу данных (я проверил содержимое в автономном браузере SQLLite), но после сохранения первого запроса, который я пытаюсь запустить, возвращается с ошибкой, описанной ниже, и я не могу найти полезную информацию в Интернете относительно этой конкретной ошибки:

Основные данные: ошибка: -executeRequest: обнаруженное исключение = база данных выглядит коррумпированной. (недопустимый первичный ключ) с userInfo = {     NSFilePath = "/Пользователи/пользователь/Library/Поддержка приложений /iPhone Simulator/7.0.3/Documents/db.sqlite";

Вопрос в том, что вызывает эту ошибку, поскольку я не могу найти никакой информации, относящейся к ней.

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

У меня есть 3 контекста управляемых объектов, все из них - NSPrivateQueueConcurrencyType, первый (A) прикреплен к координатору постоянных хранилищ, второй (B) имеет A, заданный как его родительский контекст, а третий (C) имеет B установить как родительский контекст - цепочку. Причиной этого является то, что C является записываемым контекстом, извлекает данные из сетевого источника и синхронизирует его и сохраняет его, B - это контекст, разделяемый элементами пользовательского интерфейса, и я хочу, чтобы он был отзывчивым, наконец A - фоновый контекст, разработанный выгрузить любые задержки для сохранения на диск из контекста B и C

PSC < -A < -B < -C

Если я выберу последний шаг (Saving A to PSC), то приложение отлично работает, сохраняя все в памяти и запрашивая контексты в памяти. Сбой возникает только после добавления шага сохранения и только при первом запросе, выполняемом с БД после этого сохранения. И мое сохранение, и мое выполнение выборки завернуты в executeBlock:

Вот последнее сохранение:

- (void)deepSave
{
    // Save to the Save Context which happens in memory, so the actual write to disk operation occurs on background thread
    // Expects to be called with performBlock

    NSError *error = nil;
    [super save:&error];
    NSAssert(!error, error.localizedDescription);

    // Trigger the save context to save to disk, operation will be queued and free up read only context
    NSManagedObjectContext *saveContext = self.parentContext;
    [saveContext performBlock:^{
        NSError *error = nil;
        [saveContext save:&error];
        NSAssert(!error, error.localizedDescription);
    }];
}

И вот стек выполнения (в потоке NSManagedObjectContext Queue)

#0  0x0079588a in objc_exception_throw ()
#1  0x079d98e7 in -[NSSQLiteConnection handleCorruptedDB:] ()
#2  0x078d9b8d in -[NSSQLiteConnection fetchResultSet:usingFetchPlan:] ()
#3  0x078e24a5 in newFetchedRowsForFetchPlan_MT ()
#4  0x078cd48e in -[NSSQLCore newRowsForFetchPlan:] ()
#5  0x078cca8d in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#6  0x078cc53f in -[NSSQLCore executeRequest:withContext:error:] ()
#7  0x078cbf62 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#8  0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#9  0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#10 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#11 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#12 0x013c34b0 in _dispatch_client_callout ()
#13 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#14 0x013b0422 in dispatch_barrier_sync_f ()
#15 0x0791e2a2 in _perform ()
#16 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#17 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#18 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#19 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#20 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#21 0x013c34b0 in _dispatch_client_callout ()
#22 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#23 0x013b0422 in dispatch_barrier_sync_f ()
#24 0x0791e2a2 in _perform ()
#25 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#26 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()

Ответ 1

Хорошо, я отследил его. Похоже, что в propertiesToFetch есть что-то сломанное по сравнению с NSManagedObject resultType (не должно использоваться, наша ошибка) в этой настройке - в контексте, который имеет родительский контекст вместо постоянного координатора. Этот unit test показывает, что все, что вам нужно сделать, это установить свойство для извлечения, чтобы получить эту ошибку (при выполнении запроса без свойств для корректного выполнения работ). Исправлено здесь для нас было неправильное использование свойств для извлечения:)

- (void)testManagedObjectContextDefect
{        
    NSManagedObjectContext *contextA = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    contextA.persistentStoreCoordinator = sqllitePersistentStoreCoordinator;
    NSManagedObjectContext *contextB = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    contextB.parentContext = contextA;

    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"GCSCObject" inManagedObjectContext:contextB];

    [contextB performBlockAndWait:^{
        GCSCObject *object = [[GCSCObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:contextB];
        object.serverID = @"1";
        NSError *error = nil;
        XCTAssert([contextB save:&error] && !error, @"Failed to save - %@",error); // B -> A save
    }];

    [contextA performBlock:^{
        NSError *error = nil;
        XCTAssert([contextA save:&error] && !error, @"Failed to save - %@",error); // A -> PSC, background save
    }];

    [contextB performBlockAndWait:^{
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
        NSError *error = nil;
        NSArray *results = [contextB executeFetchRequest:request error:&error];
        XCTAssert(results.count == 1 && !error, @"Fetch failed to retrieve - %@ / %@",results,error);
        GCSCObject *object = results[0];
        XCTAssert([object.serverID isEqualToString:@"1"], @"Value retrieval failed");

        // Everything passes up to here, so far so good!

        request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
        request.propertiesToFetch = @[@"serverID"]; // This is the culprit of the index crash
        results = [contextB executeFetchRequest:request error:&error];
        XCTAssert(!error, @"%@", error.localizedDescription); // !!! HERE we have a failure, assert: "Core Data: error: -executeRequest: encountered exception = The database appears corrupt.  (invalid primary key) with userInfo = { NSFilePath = "/path/db.sqlite }";
    }];
}

В этом случае GCSCObject является обычным сущностью, а serverID является одним из его параметров (неважно, какой параметр используется, или какой тип он, я пытался с несколькими. Вот описание параметра serverID я используется для этого теста:

serverID description

Сбой происходит, или мы не предоставляем andWait для сохранения в контекстах (хотя для этого было бы недействительной точка наличия очереди фона для сохранения)

Мне бы хотелось получить отзывы о том, почему это может быть так, но пока, не используя свойства для извлечения, наше приложение может работать плавно. Я рассматриваю возможность подачи Apple Bug здесь.

Ответ 2

Если вы испытываете эту ошибку при выполнении запроса на выборку, который извлекает некоторый итоговый результат (sum, max, min,...), убедитесь, что вы установили

fetchRequest.resultType = NSDictionaryResultType;

Ответ 3

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

Во-вторых, не проверяйте ошибку при сохранении. Проверьте возврат BOOL с -save:. Эта ошибка может иметь мусор в нем даже при успешном сохранении.

В-третьих, как выглядят ваши другие два сейва? Если они все находятся в частных очередях и сохраняются async, тогда вы можете столкнуться с состоянием гонки. C должен сохранять синхронно, B должен сохранять синхронно, а затем A должен быть асинхронным.