简体   繁体   English

带有NSFetchedResultsController的UITableView不会再次加载

[英]UITableView with NSFetchedResultsController Does Not Load the Second Time

Update 3 These are the logs after the first run with an empty data store. 更新3这些是第一次运行空数据存储后的日志。

2013-02-07 20:57:06.708 Five Hundred Things[14763:c07] mainMOC = <NSManagedObjectContext: 0x7475a90>
2013-02-07 20:57:06.711 Five Hundred Things[14763:1303] Import started
2013-02-07 20:57:06.712 Five Hundred Things[14763:1303] backgroundMOC = <NSManagedObjectContext: 0x8570070>
2013-02-07 20:57:06.717 Five Hundred Things[14763:c07] FRC fetch performed
2013-02-07 20:57:06.718 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.720 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.720 Five Hundred Things[14763:c07] numberOfRowsInSection returns 0
2013-02-07 20:57:06.728 Five Hundred Things[14763:1303] call contextDidSave
2013-02-07 20:57:06.736 Five Hundred Things[14763:1303] call contextDidSave
2013-02-07 20:57:06.736 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.737 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.737 Five Hundred Things[14763:c07] numberOfRowsInSection returns 5
2013-02-07 20:57:06.758 Five Hundred Things[14763:1303] call contextDidSave
2013-02-07 20:57:06.759 Five Hundred Things[14763:1303] Refresh complete
2013-02-07 20:57:06.759 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.760 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1
2013-02-07 20:57:06.761 Five Hundred Things[14763:c07] numberOfRowsInSection returns 5

Note that the FRC fetch is performed, the number of rows in the section is 0, but then after the second contextDidSave, it changes to 5 to match the number of categories in the data store. 请注意,执行FRC提取,节中的行数为0,但在第二个contextDidSave之后,它将更改为5以匹配数据存储中的类别数。

On the second run with the crash, here are the logs: 在崩溃的第二次运行中,这是日志:

2013-02-07 21:01:11.578 Five Hundred Things[14800:c07] mainMOC = <NSManagedObjectContext: 0x8225650>
2013-02-07 21:01:11.581 Five Hundred Things[14800:1303] Import started
2013-02-07 21:01:11.582 Five Hundred Things[14800:1303] backgroundMOC = <NSManagedObjectContext: 0x7439850>
2013-02-07 21:01:11.592 Five Hundred Things[14800:c07] FRC fetch performed
2013-02-07 21:01:11.594 Five Hundred Things[14800:c07] cat = Attraction
2013-02-07 21:01:11.594 Five Hundred Things[14800:c07] cat = Beverage
2013-02-07 21:01:11.595 Five Hundred Things[14800:c07] cat = Entertainment
2013-02-07 21:01:11.595 Five Hundred Things[14800:c07] cat = Hotel
2013-02-07 21:01:11.596 Five Hundred Things[14800:c07] cat = Restaurant
2013-02-07 21:01:11.597 Five Hundred Things[14800:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:01:11.598 Five Hundred Things[14800:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:01:11.599 Five Hundred Things[14800:c07] numberOfRowsInSection returns 0
2013-02-07 21:01:11.602 Five Hundred Things[14800:1303] call contextDidSave
2013-02-07 21:01:11.610 Five Hundred Things[14800:1303] call contextDidSave

The FRC is initialized, and immediately afterward the Categories are logged to show that they are indeed in the FRC. 初始化FRC,然后立即记录类别以显示它们确实在FRC中。 The number of rows in the section, however, is 0 and never gets updated. 但是,该部分中的行数为0,并且永远不会更新。 Instead the app crashes with the stack below. 相反,应用程序崩溃与下面的堆栈。

On the third and subsequent runs, this is what the log looks like: 在第三次和后续运行中,这就是日志的样子:

2013-02-07 21:03:55.560 Five Hundred Things[14815:c07] mainMOC = <NSManagedObjectContext: 0x8128860>
2013-02-07 21:03:55.563 Five Hundred Things[14815:1e03] Import started
2013-02-07 21:03:55.564 Five Hundred Things[14815:1e03] backgroundMOC = <NSManagedObjectContext: 0x822b5d0>
2013-02-07 21:03:55.569 Five Hundred Things[14815:c07] FRC fetch performed
2013-02-07 21:03:55.571 Five Hundred Things[14815:c07] cat = Attraction
2013-02-07 21:03:55.572 Five Hundred Things[14815:c07] cat = Beverage
2013-02-07 21:03:55.572 Five Hundred Things[14815:c07] cat = Entertainment
2013-02-07 21:03:55.573 Five Hundred Things[14815:c07] cat = Hotel
2013-02-07 21:03:55.573 Five Hundred Things[14815:c07] cat = Restaurant
2013-02-07 21:03:55.574 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:03:55.576 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:03:55.576 Five Hundred Things[14815:c07] numberOfRowsInSection returns 5
2013-02-07 21:03:55.581 Five Hundred Things[14815:1e03] call contextDidSave
2013-02-07 21:03:55.592 Five Hundred Things[14815:1e03] call contextDidSave
2013-02-07 21:03:55.593 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:03:55.594 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1
2013-02-07 21:03:55.595 Five Hundred Things[14815:c07] numberOfRowsInSection returns 5
2013-02-07 21:03:55.606 Five Hundred Things[14815:1e03] call contextDidSave
2013-02-07 21:03:55.606 Five Hundred Things[14815:1e03] Refresh complete

This is how the behavior should look on the second run; 这是第二次运行时的行为; the data is already in the store, the number of rows in the section returns 5, and the categories appear in the table view immediately. 数据已经存储在商店中,该部分中的行数返回5,并且类别会立即显示在表视图中。


Update 2 Here's a stack trace of the main thread, which is where the crash occurs. 更新2这是主线程的堆栈跟踪,它是崩溃发生的地方。 Since it's occurring on the main thread, I'm thinking it has something to do with the UITableView. 因为它发生在主线程上,我认为它与UITableView有关。 I'm not using NSDictionary or NSMutableDictionary in the UITableView though. 我不是在UITableView中使用NSDictionary或NSMutableDictionary。 My thought now is that numberOfRowsInSection returning 0 on the second run is causing the issue but I'm not sure how to resolve it. 我现在的想法是numberOfRowsInSection在第二次运行时返回0导致问题,但我不知道如何解决它。 It returns the correct number (5 with the data I'm using) on the third run, and seems to populate the data store correctly on the first run, so I'm confused as to why on the second run it returns 0 and doesn't update. 它在第三次运行时返回正确的数字(我正在使用的数据为5),并且似乎在第一次运行时正确填充数据存储,所以我很困惑为什么在第二次运行时它返回0并且没有不要更新。

frame #0: 0x013ede52 libobjc.A.dylib`objc_exception_throw
frame #1: 0x020330de CoreFoundation`-[__NSDictionaryM setObject:forKey:] + 158
frame #2: 0x01211d7a CoreData`-[NSFetchedResultsController(PrivateMethods) _preprocessUpdatedObjects:insertsInfo:deletesInfo:updatesInfo:sectionsWithDeletes:newSectionNames:treatAsRefreshes:] + 1994
frame #3: 0x01212ed7 CoreData`-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 2455
frame #4: 0x00b9e4f9 Foundation`__57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 40
frame #5: 0x0200a0c5 CoreFoundation`___CFXNotificationPost_block_invoke_0 + 85
frame #6: 0x01f64efa CoreFoundation`_CFXNotificationPost + 2122
frame #7: 0x00ad2bb2 Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 98
frame #8: 0x01125163 CoreData`-[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 83
frame #9: 0x011bed2f CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 367
frame #10: 0x01121128 CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _postRefreshedObjectsNotificationAndClearList] + 136
frame #11: 0x0111f8c0 CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 80
frame #12: 0x0111f869 CoreData`-[NSManagedObjectContext processPendingChanges] + 41
frame #13: 0x010f3e38 CoreData`_performRunLoopAction + 280
frame #14: 0x01f78afe CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
frame #15: 0x01f78a3d CoreFoundation`__CFRunLoopDoObservers + 381
frame #16: 0x01f567c2 CoreFoundation`__CFRunLoopRun + 1106
frame #17: 0x01f55f44 CoreFoundation`CFRunLoopRunSpecific + 276
frame #18: 0x01f55e1b CoreFoundation`CFRunLoopRunInMode + 123
frame #19: 0x01f0a7e3 GraphicsServices`GSEventRunModal + 88
frame #20: 0x01f0a668 GraphicsServices`GSEventRun + 104
frame #21: 0x00021ffc UIKit`UIApplicationMain + 1211
frame #22: 0x000022dd Five Hundred Things`main(argc=1, argv=0xbffff31c) + 141 at main.m:16
frame #23: 0x00002205 Five Hundred Things`start + 53

Update: I've managed to get an actual crash instead of just no response. 更新:我设法得到一个真正的崩溃而不是没有回应。

* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* setObjectForKey: object cannot be nil (key: _ContentChange_OldIndexPathKey)' *由于未捕获的异常'NSInvalidArgumentException'而终止app,原因:'* setObjectForKey:object不能为nil(key:_ContentChange_OldIndexPathKey)'

This SO question is the closest I could find to the error, but it discusses the value being nil instead of the key. 这个SO问题是我能找到的最接近错误的问题,但它讨论的值是nil而不是密钥。 It looks like it occurs when the Categories are being saved to the Core Data store, but all of the categories have values. 当类别被保存到Core Data存储时,它看起来会发生,但所有类别都有值。

The entity Category contains category_id - Integer 16 category_name - String 实体类别包含category_id - Integer 16 category_name - String

It has a to-many relationship with the entity Thing, but this particular part of the code is not doing anything to that relationship; 它与实体Thing有很多关系,但代码的这个特定部分对这种关系没有做任何事情; it is only setting the category_id and category_name. 它只设置category_id和category_name。 Later in the import (after the MOC save in question) is when the relationship is set. 稍后在导入(在MOC保存之后)是关系设置的时间。

Code in question from the import operation: 导入操作中的代码:

//import categories

    NSString *categoryPath = [[NSBundle mainBundle] pathForResource:@"category" ofType:@"json"];

    NSData *categoryData = [NSData dataWithContentsOfFile:categoryPath];

    NSDictionary *categoryResults = [NSJSONSerialization
                                     JSONObjectWithData:categoryData
                                     options:NSJSONReadingMutableLeaves
                                     error:&error];

    NSEntityDescription *categoryEntity = [NSEntityDescription entityForName:@"Category"
                                              inManagedObjectContext:context];

    NSMutableArray *categories = [[NSMutableArray alloc] init];

    NSString *categoryPredicateString = [NSString stringWithFormat: @"category_id == $CATEGORY_ID"];

    NSPredicate *categoryPredicate = [NSPredicate predicateWithFormat:categoryPredicateString];


    for (NSDictionary *categoryKey in categoryResults){

        NSFetchRequest *categoryFetchRequest = [[NSFetchRequest alloc] init];

        [categoryFetchRequest setEntity:categoryEntity];

        NSNumber *categoryID = [NSNumber numberWithInt:[[categoryKey objectForKey:@"category_id"] integerValue]];

        [categories addObject:categoryID];

        NSDictionary *categoryVariables = [NSDictionary dictionaryWithObject:categoryID forKey:@"CATEGORY_ID"];

        NSPredicate *catSubPredicate = [categoryPredicate predicateWithSubstitutionVariables:categoryVariables];

        [categoryFetchRequest setPredicate:catSubPredicate];

        NSArray *categoryArray = [[NSArray alloc] init];
        categoryArray = [context executeFetchRequest:categoryFetchRequest error:&error];

        Category *categoryObject = [categoryArray lastObject];
        NSNumber *categoryNum = [categoryObject valueForKey:@"category_id"];
        NSInteger categoryInt = [categoryNum integerValue];

        if (categoryInt != [[categoryKey objectForKey:@"category_id"] integerValue]){
            categoryObject = [NSEntityDescription
                              insertNewObjectForEntityForName:@"Category"
                              inManagedObjectContext:context];
            categoryObject.category_id = [NSNumber numberWithInt:[[categoryKey objectForKey:@"category_id"] intValue]];
        }
        if (categoryObject.category_name != [categoryKey objectForKey:@"category"]){
            categoryObject.category_name = [categoryKey objectForKey:@"category"];
        }

    }

    //Remove unneeded Categories from Core Data Store

    NSFetchRequest *removeUnusedCategories = [[NSFetchRequest alloc] init];
    [removeUnusedCategories setEntity:categoryEntity];
    NSArray *fetchedCategories = [context executeFetchRequest:removeUnusedCategories error:&error];

    for (Category *fetchedCategory in fetchedCategories){
        if (![categories containsObject:fetchedCategory.category_id]){
            [context deleteObject:fetchedCategory];
            NSLog(@"Object deleted");
        }
    }

    if (![context save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }

The [context save] occurs on the background MOC and is synced to the main MOC (in the app delegate) through the notification center. [context save]发生在后台MOC上,并通过通知中心同步到主MOC(在app委托中)。 It listens for NSManagedObjectContextDidSaveNotification and runs mergeChangesFromContextDidSaveNotification: on the main MOC. 它侦听NSManagedObjectContextDidSaveNotification并在主MOC上运行mergeChangesFromContextDidSaveNotification: .

The first run and the third run work perfectly. 第一次运行和第三次运行完美。 It always occurs on the second run. 它总是在第二次运行时发生。


I'm using Core Data on an iOS project and so far it's working well except for one problem. 我在iOS项目中使用Core Data,到目前为止除了一个问题外它运行良好。

The app populates the Core Data store from JSON files and the initial UITableViewController loads up with animation as it should. 该应用程序从JSON文件填充Core Data存储,初始UITableViewController加载动画。 However, the second time the app launches, the initial UITableView is blank. 但是,第二次启动应用程序时,初始UITableView为空白。 I've checked in multiple places and the data is in the Core Data store when the second launch begins, but none of the UITableView or NSFetchedResultsController methods are called. 我已经检查了多个位置,并且在第二次启动时数据位于Core Data存储中,但是没有调用UITableView或NSFetchedResultsController方法。

On the first launch, the number of rows in section returns 0 but after the Core Data store is loaded returns 5 as it should. 在第一次启动时,节中的行数返回0但在加载Core Data存储之后返回5应该。 On the second launch, the number of rows in the section (only one section) returns 0 and doesn't update. 在第二次启动时,节中的行数(仅一个节)返回0并且不更新。 On the third and all subsequent launch, the number of rows in the section returns 5 as it should. 在第三次和随后的所有启动中,该部分中的行数将返回5。

Neither the UITableView's cellForRowAtIndexPath nor the NSFetchedResultsController's didChangeObject methods are called on the second launch of the app. 在第二次启动应用程序时,UITableView的cellForRowAtIndexPath和NSFetchedResultsController的didChangeObject方法都不会被调用。 The UITableViewController is the UITableViewDelegate , UITableViewDataSource , and NSFetchedResultsControllerDelegate . UITableViewController是UITableViewDelegateUITableViewDataSourceNSFetchedResultsControllerDelegate

As suggested in the Core Data guidelines, the app delegate and table view controller share a managed object context while the data loading is being done on a background MOC in another thread. 正如核心数据准则中所建议的那样,app委托和表视图控制器共享一个托管对象上下文,而数据加载是在另一个线程中的后台MOC上完成的。 These are being synced when the context's save method is called through the mergeChangesFromContextDidSaveNotification: . 当通过mergeChangesFromContextDidSaveNotification:调用上下文的save方法时,这些是同步的mergeChangesFromContextDidSaveNotification:

To reproduce, I delete the app from the simulator, run once and the database populates and the app displays correctly. 要重现,我从模拟器中删除应用程序,运行一次,数据库填充并正确显示应用程序。 I stop the app and run again and nothing displays. 我停止应用程序并再次运行,没有任何显示。 I stop the app and run a third time and it displays correctly. 我停止应用程序并运行第三次并正确显示。

All of this seems to work correctly except for the second time launching the app. 除了第二次启动应用程序外,所有这些似乎都能正常工作。 The first and third times work properly. 第一次和第三次正常工作。 What am I missing? 我错过了什么?

As for my code, I'm not sure what to put here. 至于我的代码,我不知道该放什么。 Let's start with the UITableViewController's implementation. 让我们从UITableViewController的实现开始。

@implementation FTWTMasterViewController

@synthesize managedObjectContext;
@synthesize categoryController = _categoryController;
@synthesize catLocViewController;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (NSFetchedResultsController *)categoryController {
    if (_categoryController != nil) {
        return _categoryController;
    }

    NSLog(@"tableview MOC = %@", self.managedObjectContext);

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                              initWithKey:@"category_name" ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.managedObjectContext
                                          sectionNameKeyPath:nil
                                                   cacheName:@"CategoryTable"];
    _categoryController = theFetchedResultsController;
    _categoryController.delegate = self;

    return _categoryController;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.dataSource = self;
    self.tableView.delegate = self;

    NSError *error;
    if (![[self categoryController] performFetch:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }

    NSLog(@"Fetch called");

    self.title = @"Categories";
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSLog(@"number of sections = %lu", (unsigned long)[[self.categoryController sections] count]);
    return [[self.categoryController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id  sectionInfo =
        [[_categoryController sections] objectAtIndex:section];

    NSLog(@"numberOfObjects = %lu", (unsigned long)[sectionInfo numberOfObjects]);
    return [sectionInfo numberOfObjects];
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    Category *category = [_categoryController objectAtIndexPath:indexPath];
    cell.textLabel.text = category.category_name;
    NSLog(@"config cell %@", category.category_name);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"tableView setup");
    static NSString *CellIdentifier = @"categoryCell";

    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Set up the cell...
    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    Category *aCategory = [self.categoryController objectAtIndexPath:indexPath];

    if (self.catLocViewController == nil){
        FTWTCatLocationViewController *aCatLocController = [[FTWTCatLocationViewController alloc] init];
        self.catLocViewController = aCatLocController;
    }

    self.catLocViewController.selectedCat = aCategory;
    aCategory = nil;

    self.catLocViewController.managedObjectContext = self.managedObjectContext;

    [self.navigationController pushViewController:self.catLocViewController animated:YES];

    self.catLocViewController = nil;
}

#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {

    NSLog(@"didChangeObject");

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray
                                               arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray
                                               arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )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;
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

@end

Contrary to what I assumed in above comment, the same effect can happen if a sectionNameKeyPath is specified to create table view sections (I could reproduce this with a test program). 与我在上面的注释中假设的相反,如果指定了sectionNameKeyPath来创建表视图部分(我可以使用测试程序重现这一点),则会发生相同的效果。

When the app is started the first time, the following happens: 当应用程序第一次启动时,会发生以下情况:

  1. A persistent store file "appname.sqlite" is created. 创建持久性存储文件“appname.sqlite”。
  2. A fetched results controller for the table view is created with the cacheName parameter set, so that a section cache file is created. 使用cacheName参数集创建表视图的获取结果控制器,以便创建节缓存文件。 At this point, all sections are empty. 此时,所有部分都是空的。
  3. A background MOC is created that reads some JSON data from a resource file and add objects to the context. 创建后台MOC,从资源文件中读取一些JSON数据并将对象添加到上下文中。
  4. The background MOC is saved. 后台MOC已保存。

(Btw. the cache file is (顺便说一句,缓存文件是

   Library/Caches/<bundle-id>/.CoreDataCaches/SectionInfoCaches/<tablename>/sectionInfo

in the application bundle.) 在应用程序包中。)

When the app is started the second time, the fetched results controller checks whether the section info cache is still valid or has to be recreated. 第二次启动应用程序时,获取的结果控制器会检查部分信息缓存是否仍然有效或是否必须重新创建。 According to the documentation it compares the modification times of the persistent store file and the section cache file. 根据文档,它比较了持久存储文件和节缓存文件的修改时间。

Now the interesting part: If (in the first run) the creation of the store file (step 1) and saving the updated context (step 4) happen in the same second , then the modification date of the store file is not changed in step 4!! 现在有趣的部分:如果(在第一次运行中)创建存储文件(步骤1)并保存更新的上下文(步骤4) 发生在同一秒 ,那么商店文件的修改日期不会在步骤中更改 4!

Therefore the section cache file is still seen as valid and not recreated . 因此,部分缓存文件仍被视为有效且未重新创建 Since all sections were empty (in step 2), the FRC uses this cached information and displays only empty sections. 由于所有部分都是空的(在步骤2中),FRC使用此缓存信息并仅显示空部分。

The background MOC is started again and saves the context. 后台MOC再次启动并保存上下文。 Now the store file has a new modification date, therefore the sections and rows are displayed correctly in the third run of the app. 现在,商店文件有一个新的修改日期,因此在应用程序的第三次运行中正确显示了部分和行。

To confirm my "theory", I did a manual "touch" of the store file between first and second run to enforce a changed modification date. 为了确认我的“理论”,我在第一次和第二次运行之间手动“触摸”了商店文件,以强制执行更改的修改日期。 All sections and rows were then displayed correctly. 然后正确显示所有部分和行。

(I tested this only in the iPhone Simulator. I don't know if the HFS+ file system generally has a 1 second resolution of the modification date, or if SQLite does something special here. I will try to investigate that later.) (我仅在iPhone模拟器中对此进行了测试。我不知道HFS +文件系统是否通常具有1秒的修改日期分辨率,或者SQLite是否在这里做了一些特殊的事情。我稍后会尝试对此进行调查。)

Conclusion: If the creation of the store file and saving modified data happen in the same second, a section info cache file might not be regenerated if necessary. 结论:如果创建存储文件并保存修改的数据在同一秒内发生,则必要时可能不会重新生成节信息缓存文件。

I ran into the same problem. 我遇到了同样的问题。 When I loaded the data, the first load was fine. 当我加载数据时,第一次加载很好。 When I restarted my app, the data disappeared from the table. 当我重新启动我的应用程序时,数据从表中消失。 These were my counts: 这些是我的理由:

self.fetchedResultsController.fetchedObjects : 8 self.fetchedResultsController.fetchedObjects :8
[self.fetchedResultsController.sections count] : 1 [self.fetchedResultsController.sections count] :1
[self.fetchedResultsController.sections[0] numberOfObjects] : 0 [self.fetchedResultsController.sections[0] numberOfObjects] :0

I have the ability to change the sort in the UI and after changing the sort and then changing it back again, the issue disappeared, which definitely pointed to a caching issue. 我有能力在UI中更改排序,在更改排序然后再将其更改后,问题就消失了,这肯定指出了缓存问题。

Calling 调用

[NSFetchedResultsController deleteCacheWithName:nil];

will delete all caches. 将删除所有缓存。

I also ended up choosing not to cache for now. 我最后还选择不缓存。 I don't think I will store tens of thousands of records in my database for these tables, so I'm not sure if caching will improve anything. 我不认为我会在我的数据库中为这些表存储数万条记录,因此我不确定缓存是否会改进任何内容。 Setting cacheName to nil will prevent any caching for NSFetchedResultsController . 设置cacheNamenil将阻止任何缓存NSFetchedResultsController

self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                    managedObjectContext:self.context
                                                                      sectionNameKeyPath:sectionIdentifier
                                                                               cacheName:nil];

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM