Не удается обновить NSManagedObject из-за исключения NSObjectInaccessible.

Я создаю приложение для отображения ресурсов (PDF, видео и т. д.).

Он начинается с загрузки JSON и его анализа в Core Data Objects ‹-- Эта часть работает нормально.

Эти объекты представляют собой иерархический набор узлов, отношения между которыми установлены в моей модели. каждый узел может быть либо ФАЙЛОМ, либо ПАПКОЙ. <-- Нет проблем.

Затем у меня есть методы экземпляра, встроенные в мои подклассы NSManagedObject, которые будут загружать файл, связанный с этим объектом (т.е. PDF). Затем он устанавливает

self.isAvailable = [NSNumber numberWithBool:YES];

Тем временем у меня есть UITableView, который отображает список активов. в конечном итоге он будет обновляться в режиме реального времени, но сейчас у меня проблема. Сначала у меня был контроллер представления, который хранил указатель на объект CoreData, представляющий отображаемую папку, но оказалось, что если контекст обновляется, указатель становится недействительным (т. е. не дает сбоя).

Основные данные не очень конкретны в том, в чем проблема или даже где она происходит, но, похоже, происходит сбой, когда я устанавливаю isAvailable с помощью

*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x1d5f9e50 <x-coredata://EDE66B97-B142-4E87-B445-76CAB965B676/Node/p58>''

Я чувствую, что проблема в том, что я не должен просто хранить сильную ссылку на основной объект данных в качестве моей модели. Есть ли лучший (менее аварийный) способ сделать это?

Я начал играть с NSFetchedResultsController и вместо этого использовать objectID, но я еще ничего не добился.

- (void)populateChildren {

    NSString * urlString = [NSString stringWithFormat:@"%@/%@", [CMPConstants hostURLString], self.SBUCode];

    NSURL *url = [NSURL URLWithString:urlString];

    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:self.downloadQueue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
        if (data) {
            NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
            [self processParsedObject:dict];
        } else {
            NSLog(@"%@", urlString);
        }


    }];
}

#pragma mark - Parse JSON into NSManagedObjects

- (void)processParsedObject:(id)object {
    [self processParsedObject:object depth:0 parent:nil key:nil];
    [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
}

- (void)processParsedObject:(id)object depth:(int)depth parent:(Node *)parent key:(NSString*)key {
    if ([object isKindOfClass:[NSDictionary class]]) {

        if (depth == 0) {    

                // Grab content node if depth is 0;
            object = [object valueForKey:@"content"];
        }


            // FIXME: Change this to a real primary key once we get one.
        static NSString * primaryKey = @"name";

            // Look for existing object
        Node * testNode = [Node MR_findFirstByAttribute:primaryKey withValue:[object valueForKey:primaryKey]];

            // Create new node pointer
        Node * newNode;

        if (testNode) {
                // Update existing Node
            newNode = testNode;
        } else {
                // Build a new Node Object
            newNode = [Node MR_createEntity];
            newNode.isAvailable = [NSNumber numberWithBool:NO];
        }

            // Get keys
        NSArray * keys = @[@"name",
                           @"type",
                           @"index",
                           @"size",
                           @"videoDemensions",
                           @"videoId",
                           @"fileName",
                           @"fileType",
                           @"path"];

        if ([[object valueForKey:@"type"] isEqual:[NSNull null]]) {
            NSLog(@"%@", object);
        }

            // Loop to set value for keys.
        for (NSString * key in keys) {

            id value = [object valueForKey:key];

            if (![[object valueForKey:key] isKindOfClass:[NSNull class]]) {
                [newNode setValue:value forKey:key];
            }
        }

            // Set calculated properties.
        [newNode setSbu:[self SBUCode]];
        [newNode setParent:parent];
        [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
            // Sync local file.
        if (!newNode.isAvailable.boolValue) {
            [newNode aquireFileInQueue:self.downloadQueue];
        }


            // Process children
        for(NSString * newKey in [object allKeys]) {
            id child = [object objectForKey:newKey];
            [self processParsedObject:child depth:depth+1 parent:newNode key:newKey];
        }

    } else if ([object isKindOfClass:[NSArray class]]) {
        for(id child in object) {
            [self processParsedObject:child depth:depth+1 parent:parent key:nil];
        }
    } else {
            // Nothing here, this processes each field.
    }
}

Этот метод является методом экземпляра класса Node.

- (void)aquireFileInQueue:(NSOperationQueue *)queue {

    if ([self.type isEqualToString:@"VIDEO"]) {

            // Videos are available, but not downloaded.
        self.isAvailableValue = YES;
        return;
    }

    if (self.path == nil || self.fileName == nil) {
        NSLog(@"Path or Filename for %@ was nil", self.name);
        return;
    }

        // Build the download URL !! MAKE SURE TO ADD PERCENT ESCAPES, this will protect against spaces in the file name
        // Also make sure to slash-separate the path and fileName
    NSURL * downloadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@",
                                                [self.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
                                                [self.fileName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];

        // Build the download request
    NSURLRequest * downloadRequest = [NSURLRequest requestWithURL:downloadURL];

        // FIXME: Authentication Code for JSON service

        // Show network activity indicator
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

        // Send Asynchronus Request for fileData
    [NSURLConnection sendAsynchronousRequest:(NSURLRequest *)downloadRequest queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {

            // Hide network activity indicatior
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

            // Cast URL Response to HTTPURLResponse
        NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;

            // If statusCode is 200 (successful) and data is not nil, save data
        if (httpResponse.statusCode == 200 && data) {
            [data writeToURL:[self fileURL] atomically:NO];
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [self setIsAvailable:[NSNumber numberWithBool:YES]];
            }];

        }
    }];
}

- (void)prepareForDeletion {

        // Remove file from Filesystem
    [[NSFileManager defaultManager] removeItemAtURL:[self fileURL] error:nil];
}

- (NSURL *)fileURL {
        // Return local file URL
    return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", [Node applicationDocumentsDirectory], self.fileName]];
}

person Weston    schedule 24.04.2013    source источник
comment
Как обновляется контекст? Вы объединяете изменения из уведомления о сохранении? Что-то другое?   -  person Tom Harrington    schedule 24.04.2013
comment
Похоже на проблему с потоками? Вы делитесь NSManagedObject между потоками? Если это так, то, по мнению Apple, нет-нет. Вы должны использовать objectID так же, как вы начинаете работать. NSFetchedResultsController — хороший способ, если вы хотите убедиться, что ваше табличное представление остается в курсе изменений, которые происходят в другом контексте/потоке.   -  person Chris Wagner    schedule 25.04.2013
comment
Конкретный класс NSManagedObject имеет метод с именем -(void)aquireFile. Этот метод загружает файл в отдельную очередь, а затем отправляет обратно в mainQueue для установки isAvailable. Насколько я понимаю, это не должно вызывать проблем с очередью или контекстом. может я что-то упустил?   -  person Weston    schedule 25.04.2013
comment
Я добавил немного кода. Извините за беспорядок, это не так элегантно, как я изначально написал, потому что я пытаюсь отследить эту ошибку.   -  person Weston    schedule 25.04.2013


Ответы (1)


Я не знаком с MagicalRecords

Ошибка "Не удалось выполнить заполнение ошибки" возникает, когда контекст содержит объект без ошибок (объект-заглушка), но фактический объект в базе данных не существует (удален или никогда не сохранялся).

Мой первый совет:
если вы работаете в многопоточной среде, попробуйте удерживать объекты с ошибкой.
используйте -existingObjectWithId:error: и запросы на выборку с помощью:

[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setIncludesPropertyValues:YES];
[fetchRequest setRelationshipKeyPathsForPrefetching:/*relationships you can afford to prefetch*/];

Мой второй совет (для отладки вашей проблемы):
Печатайте свой deletedObjects набор перед каждым сохранением в магазине, чтобы увидеть, какой контекст вызвал ошибку.

Мой третий совет:
объединяйте изменения с основным контекстом (думаю, MagicalRecords сделает это за вас).

примечание 1: удаление может подразумеваться (вы не используете явно deleteObject:, например, устанавливая отношение в режиме каскада/запрета)

примечание 2: вы не можете избежать этого исключения в многопоточной среде (AFAIK), если вы не передаете все свои сохранения через основной контекст (используя parentContext) или всегда используя предварительно выбранные объекты (не используя отношения напрямую).

person Dan Shelly    schedule 25.04.2013
comment
Я запутался, так как -(void)aquireFile является методом объекта Node, как он может не существовать? Есть ли что-то, что мне нужно сделать, чтобы зафиксировать объект в контексте, прежде чем сработают ошибки? - person Weston; 25.04.2013
comment
Мои объекты определенно правильно помещаются в контекст. Если я открою файл .sqlite в sqlite3, я увижу все свои записи. - person Weston; 25.04.2013
comment
Я кое-что проверю и вернусь к вам. тем временем я заметил, что вы изменяете объект, принадлежащий контексту BG, в основном потоке в блоке завершения загрузки. Я советую вам изменить порядок вещей, выполнить загрузку и по завершении повторно загрузить объект в контексте BG и, если он существует, обновить его состояние. - person Dan Shelly; 25.04.2013
comment
Я понял, моя первоначальная загрузка должна была выполнять processParsedObject в mainQueue, а не в downloadQueue. Спасибо, что указали на это. - person Weston; 25.04.2013