Неправильный номер разделов NSFetchedResultsController

Мои шаги:

  1. Нажмите ТВЦ.
  2. Инициируйте новый FRC с контекстом управляемого объекта.
  3. Выполните выборку.
  4. Получилось две секции по одному ряду в каждой секции.
  5. Выполнить запрос на выборку, используемый в FRC.
  6. Получил массив из двух элементов.
  7. Удалите последнюю строку и сохраните MOC.
  8. Поп ТВЦ.
  9. Нажмите ТВЦ.
  10. Инициируйте новый FRC с тем же MOC.
  11. Выполните выборку.
  12. Получил ДВА раздела.
  13. Выполнить запрос на выборку, используемый в FRC.
  14. Получил массив с ОДНИМ элементом.

Я безуспешно пытался вызвать deleteCacheWithName перед выполнением выборки.

Я не понимаю, что делать.

Журнал сбоев:

2013-01-10 12:27:47.948 MyApp[59830:c07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[_PFArray objectAtIndex:]: index (1) beyond bounds (1)'

Строка кода, где приложение вылетает:

Card *card = [self.fetchedResultsController objectAtIndexPath:indexPath];

Вот CoreDataTableViewController.m из курсов Стэнфорда:

//
//  CoreDataTableViewController.m
//
//  Created for Stanford CS193p Fall 2011.
//  Copyright 2011 Stanford University. All rights reserved.
//

#import "CoreDataTableViewController.h"

@interface CoreDataTableViewController()

@property (nonatomic) BOOL beganUpdates;

@end

@implementation CoreDataTableViewController

#pragma mark - Fetching

- (void)performFetch {
    if (self.fetchedResultsController) {
        NSLog(@"TVC: Perform fetch");

        NSError *error;
        [self.fetchedResultsController performFetch:&error];

        if (error)
            NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);

        NSLog(@"TVC: Number of fetched objects in FRC: %d", self.fetchedResultsController.fetchedObjects.count);
        NSLog(@"TVC: Number of sections in FRC: %d", self.fetchedResultsController.sections.count);
    } else {
        NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    }

    [self.tableView reloadData];
}
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc {
    NSFetchedResultsController *oldfrc = _fetchedResultsController;
    if (newfrc != oldfrc) {
        _fetchedResultsController = newfrc;
        newfrc.delegate = self;
        if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
            self.title = newfrc.fetchRequest.entity.name;
        }
        if (newfrc) {
            [self performFetch];
        } else {
            [self.tableView reloadData];
        }
    }
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSUInteger numberOfSections = self.fetchedResultsController.sections.count;
    NSLog(@"TVC: DataSource, number of sections: %d", numberOfSections);

    return numberOfSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSArray *sections = self.fetchedResultsController.sections;
    if (sections.count == 0) {
        NSLog(@"TVC: DataSource, there are no sections");
        return 0;
    }

    id<NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
    NSUInteger numberOfRows = sectionInfo.numberOfObjects;
    NSLog(@"TVC: DataSource, number of rows: %d, in section: %d", numberOfRows, section);

    return numberOfRows;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [self.fetchedResultsController sectionIndexTitles];
}

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) {
        [self.tableView beginUpdates];
        self.beganUpdates = YES;
    }
}
- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type {
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}
- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeUpdate:
                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeMove:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    if (self.beganUpdates)
        [self.tableView endUpdates];
}
- (void)endSuspensionOfUpdatesDueToContextChanges {
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend {
    if (suspend) {
        _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
    } else {
        [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
    }
}

- (void)logFetchedSections:(NSArray *)sections {
}

@end

Вот часть моей модели:

//
//  TLModel.m
//

...

@property (nonatomic, strong) TLCoreData *coreData;

...


- (void)setupFetchedResultsControllerCardsListWithCompletion:(void (^)(NSFetchedResultsController *result))completion {
    [self.coreData performWithDocument:^(UIManagedDocument *document) {
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Card"];
        request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"named"
                                                                                         ascending:YES
                                                                                          selector:@selector(localizedCaseInsensitiveCompare:)]];
        NSError *error;
        NSArray *matches = [document.managedObjectContext executeFetchRequest:request error:&error];
        NSLog(@"Model: Execute fetch request, array count: %d", matches.count);

        NSFetchedResultsController *result = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                                 managedObjectContext:document.managedObjectContext
                                                                                   sectionNameKeyPath:@"named"
                                                                                            cacheName:nil];
        NSLog(@"Model: Created new FRC: %@", result);

        completion(result);
    }];
}

...

Вот ТВЦ:

//
//  TLMyTableViewController.m
//

...


- (void)prepareFetchedResultController {
    [self.model setupFetchedResultsControllerCardsListWithCompletion:^(NSFetchedResultsController *result){
        self.fetchedResultsController = result;
    }];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"My Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    // Configure the cell...
    Card *card = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = card.number;
    cell.detailTextLabel.text = card.option;

    return cell;
}
- (void)viewDidLoad {
    NSLog(@"TVC: Did load: %@", self);
    [self prepareFetchedResultController];
}

...

TLCoreData:

//
//  TLCoreData.m
//

#import "TLCoreData.h"
#import <CoreData/CoreData.h>
#import "TLManagedDocument.h"

@interface TLCoreData()

@property (nonatomic, strong) TLManagedDocument *document;

@end

@implementation TLCoreData

/*
static TLCoreData *_sharedInstance;

+ (TLCoreData *)sharedDocumentHandler {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        _sharedInstance = [[self alloc] init];
    });

    return _sharedInstance;
}
*/

- (TLManagedDocument *)document {
    if (!_document) {
        NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
        url = [url URLByAppendingPathComponent:@"DataUsage.db"];
        _document = [[TLManagedDocument alloc] initWithFileURL:url];
    }

    return _document;
}

- (void)performWithDocument:(OnDocumentReady)onDocumentReady {
    void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
        if(success) {
#ifdef DEBUG
            NSLog(@"Current context: %@", self.document.managedObjectContext);
#endif
            onDocumentReady(self.document);
        }
#ifdef DEBUG
        else
            NSLog(@"Core Data: Managed document does not ready");
#endif
    };

    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
#ifdef DEBUG
        NSLog(@"Core Data: Initialized document: %@", self.document.fileURL);
#endif
        [self.document saveToURL:self.document.fileURL
                forSaveOperation:UIDocumentSaveForCreating
               completionHandler:OnDocumentDidLoad];
    } else if (self.document.documentState == UIDocumentStateClosed) {
#ifdef DEBUG
        NSLog(@"Core Data: Opened document: %@", self.document.fileURL);
#endif
        [self.document openWithCompletionHandler:OnDocumentDidLoad];
    } else if (self.document.documentState == UIDocumentStateNormal) {
        OnDocumentDidLoad(YES);
    }
}

- (void)objectsDidChange:(NSNotification *)notification {
#ifdef DEBUG
    NSLog(@"Core Data: Objects changed in context: %@", self.document.managedObjectContext);
#endif
}
- (void)contextDidSave:(NSNotification *)notification {
#ifdef DEBUG
    NSLog(@"Core Data: Context saved: %@", self.document.managedObjectContext);
#endif
}

- (id)init {
    self = [super init];
    if (self) {

        // Set our document up for automatic migrations
        NSDictionary *options = @{
            NSMigratePersistentStoresAutomaticallyOption : @YES,
            NSInferMappingModelAutomaticallyOption : @YES
        };
        self.document.persistentStoreOptions = options;

        // Register for notifications
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(objectsDidChange:)
                                                     name:NSManagedObjectContextObjectsDidChangeNotification
                                                   object:self.document.managedObjectContext];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextDidSave:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:self.document.managedObjectContext];
    }

    return self;
}
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:self.document.managedObjectContext];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextObjectsDidChangeNotification
                                                  object:self.document.managedObjectContext];
}

@end

Журналы:

2013-01-10 12:27:43.291 MyApp[59830:c07] App: Launched
2013-01-10 12:27:43.354 MyApp[59830:c07] Core Data: Opened document: file://localhost/Users/itsme/Library/Application%20Support/iPhone%20Simulator/6.0/Applications/998028DF-FACC-4EFF-A5C7-B286B91F1100/Documents/DataUsage.db/
2013-01-10 12:27:43.354 MyApp[59830:c07] Model: Logging started
2013-01-10 12:27:43.357 MyApp[59830:c07] App: Did become active
2013-01-10 12:27:43.371 MyApp[59830:c07] Current context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:43.374 MyApp[59830:c07] Current context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:44.746 MyApp[59830:c07] TVC: Did load: <TLMyTableViewController: 0x8566100>
2013-01-10 12:27:44.746 MyApp[59830:c07] Current context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:44.747 MyApp[59830:c07] Model: Execute fetch request, array count: 2
2013-01-10 12:27:44.748 MyApp[59830:c07] Model: Created new FRC: <NSFetchedResultsController: 0x747bbb0>
2013-01-10 12:27:44.748 MyApp[59830:c07] TVC: Perform fetch
2013-01-10 12:27:44.750 MyApp[59830:c07] TVC: Number of fetched objects in FRC: 2
2013-01-10 12:27:44.750 MyApp[59830:c07] TVC: Number of sections in FRC: 2
2013-01-10 12:27:44.751 MyApp[59830:c07] TVC: DataSource, number of sections: 2
2013-01-10 12:27:44.752 MyApp[59830:c07] TVC: DataSource, number of rows: 1, in section: 1
2013-01-10 12:27:44.752 MyApp[59830:c07] TVC: DataSource, number of rows: 1, in section: 0
2013-01-10 12:27:46.346 MyApp[59830:c07] Current context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:46.347 MyApp[59830:c07] Model: Deleting a row
2013-01-10 12:27:46.349 MyApp[59830:c07] TVC: DataSource, number of sections: 1
2013-01-10 12:27:46.349 MyApp[59830:c07] TVC: DataSource, number of sections: 1
2013-01-10 12:27:46.349 MyApp[59830:c07] TVC: DataSource, number of rows: 1, in section: 0
2013-01-10 12:27:46.350 MyApp[59830:c07] Core Data: Objects changed in context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:46.351 MyApp[59830:c07] Core Data: Context saved: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:47.941 MyApp[59830:c07] TVC: Did load: <TLMyTableViewController: 0x747d5e0>
2013-01-10 12:27:47.941 MyApp[59830:c07] Current context: <NSManagedObjectContext: 0x8185590>
2013-01-10 12:27:47.942 MyApp[59830:c07] Model: Execute fetch request, array count: 1
2013-01-10 12:27:47.942 MyApp[59830:c07] Model: Created new FRC: <NSFetchedResultsController: 0x74b9410>
2013-01-10 12:27:47.942 MyApp[59830:c07] TVC: Perform fetch
2013-01-10 12:27:47.944 MyApp[59830:c07] TVC: Number of fetched objects in FRC: 1
2013-01-10 12:27:47.944 MyApp[59830:c07] TVC: Number of sections in FRC: 2
2013-01-10 12:27:47.944 MyApp[59830:c07] TVC: DataSource, number of sections: 2
2013-01-10 12:27:47.945 MyApp[59830:c07] TVC: DataSource, number of rows: 1, in section: 1
2013-01-10 12:27:47.945 MyApp[59830:c07] TVC: DataSource, number of rows: 1, in section: 0
2013-01-10 12:27:47.948 MyApp[59830:c07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[_PFArray objectAtIndex:]: index (1) beyond bounds (1)'

Эта строка говорит о неправильном количестве секций в массиве:

2013-01-10 12:27:47.944 MyApp[59830:c07] TVC: Number of sections in FRC: 2

Исходный код этого журнала:

NSLog(@"TVC: Number of sections in FRC: %d", self.fetchedResultsController.sections.count);

person adnako    schedule 10.01.2013    source источник
comment
Предоставьте код, пожалуйста.   -  person Lorenzo B    schedule 10.01.2013
comment
Я пытаюсь упростить исходники, подождите, пожалуйста   -  person adnako    schedule 10.01.2013
comment
Попробуйте также с решением, которое я написал. Спасибо.   -  person Lorenzo B    schedule 10.01.2013
comment
Не могли бы вы удалить журналы, оставив только краш, который вы получаете? Спасибо.   -  person Lorenzo B    schedule 10.01.2013
comment
Я переместил весь лог в конец и оставил только строку сбоя   -  person adnako    schedule 10.01.2013
comment
Я добавил редактирование для вас.   -  person Lorenzo B    schedule 10.01.2013


Ответы (1)


Вы должны предоставить нам другие данные.

А пока я предлагаю взглянуть на NSFetchedResultsControllerDelegate класс. Вы можете найти метод, который вам нужно реализовать, в справочнике по классам.

Если вы установите делегата для своего экземпляра NSFetchedResultsController, например

_fetchedController.delegate = self;

затем делегат будет реагировать на изменения, внесенные в модель Core Data (добавление или удаление разделов, добавление, удаление, перемещение или обновление строк).

Например, если вы реализуете

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                            withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

и вы удалите или добавите раздел, изменения будут зафиксированы для вас.

Надеюсь, поможет.

Изменить

Я предполагаю, что если TLMyTableViewController расширяет CoreDataTableViewController, вы вызываете не тот метод в

- (void)prepareFetchedResultController {
    [self.model setupFetchedResultsControllerCardsListWithCompletion:^(NSFetchedResultsController *result){
        self.fetchedResultsController = result;
    }];
}

попробуйте вместо этого использовать

[self setFetchedResultsController:result];
person Lorenzo B    schedule 10.01.2013
comment
Я устанавливаю делегата в установщике FRC, и он вызывает метод didChangeSection. В отредактированном TVC все работает нормально. Затем я вставляю отредактированный TVC и нажимаю новый с новым FRC (но инициализированным с тем же MOC). И новый TVC не может отображать себя, потому что FRC дает массив с неправильным количеством секций. - person adnako; 10.01.2013
comment
@cirroz Предоставьте код. Потому что довольно сложно понять, что происходит. Также удалите журналы и поставьте соответствующий код. Спасибо. - person Lorenzo B; 10.01.2013
comment
К сожалению, обновление не помогло - ТВЦ вызывает сеттер в обоих вариантах. В любом случае спасибо за помощь. - person adnako; 10.01.2013
comment
Я думаю, что FRC получил какие-то кешированные результаты. Я не понимаю, где он может их получить. FRC не находится в состоянии согласованности. Одна часть FRC возвращает одну извлеченную запись, и правда, эта запись лежит в одной секции. Но следующая часть FRC думает, что у нее все еще есть две секции, но на самом деле это не так. - person adnako; 10.01.2013
comment
Я не использую кеш, я устанавливаю nil для аргумента кеша при инициализации FRC: NSFetchedResultsController *result = [[NSFetchedResultsController alloc] initWithFetchRequest:request manageObjectContext:document.managedObjectContext sectionNameKeyPath:@named cacheName:nil]; - person adnako; 10.01.2013
comment
Привет @lorenzo-b, мой пост набрал тысячу просмотров, сказал SO. И я оглянулся на это. Теперь я думаю, что мне пришлось явно вызвать установщик fetchedresultcontroller в основной очереди, завернутой в метод dispatch_async(). Нужно проверить. - person adnako; 12.05.2016