簡體   English   中英

帶有NSFetchedResultsController的UITableView不會再次加載

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

更新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

請注意,執行FRC提取,節中的行數為0,但在第二個contextDidSave之后,它將更改為5以匹配數據存儲中的類別數。

在崩潰的第二次運行中,這是日志:

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

初始化FRC,然后立即記錄類別以顯示它們確實在FRC中。 但是,該部分中的行數為0,並且永遠不會更新。 相反,應用程序崩潰與下面的堆棧。

在第三次和后續運行中,這就是日志的樣子:

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

這是第二次運行時的行為; 數據已經存儲在商店中,該部分中的行數返回5,並且類別會立即顯示在表視圖中。


更新2這是主線程的堆棧跟蹤,它是崩潰發生的地方。 因為它發生在主線程上,我認為它與UITableView有關。 我不是在UITableView中使用NSDictionary或NSMutableDictionary。 我現在的想法是numberOfRowsInSection在第二次運行時返回0導致問題,但我不知道如何解決它。 它在第三次運行時返回正確的數字(我正在使用的數據為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

更新:我設法得到一個真正的崩潰而不是沒有回應。

*由於未捕獲的異常'NSInvalidArgumentException'而終止app,原因:'* setObjectForKey:object不能為nil(key:_ContentChange_OldIndexPathKey)'

這個SO問題是我能找到的最接近錯誤的問題,但它討論的值是nil而不是密鑰。 當類別被保存到Core Data存儲時,它看起來會發生,但所有類別都有值。

實體類別包含category_id - Integer 16 category_name - String

它與實體Thing有很多關系,但代碼的這個特定部分對這種關系沒有做任何事情; 它只設置category_id和category_name。 稍后在導入(在MOC保存之后)是關系設置的時間。

導入操作中的代碼:

//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]);
    }

[context save]發生在后台MOC上,並通過通知中心同步到主MOC(在app委托中)。 它偵聽NSManagedObjectContextDidSaveNotification並在主MOC上運行mergeChangesFromContextDidSaveNotification: .

第一次運行和第三次運行完美。 它總是在第二次運行時發生。


我在iOS項目中使用Core Data,到目前為止除了一個問題外它運行良好。

該應用程序從JSON文件填充Core Data存儲,初始UITableViewController加載動畫。 但是,第二次啟動應用程序時,初始UITableView為空白。 我已經檢查了多個位置,並且在第二次啟動時數據位於Core Data存儲中,但是沒有調用UITableView或NSFetchedResultsController方法。

在第一次啟動時,節中的行數返回0但在加載Core Data存儲之后返回5應該。 在第二次啟動時,節中的行數(僅一個節)返回0並且不更新。 在第三次和隨后的所有啟動中,該部分中的行數將返回5。

在第二次啟動應用程序時,UITableView的cellForRowAtIndexPath和NSFetchedResultsController的didChangeObject方法都不會被調用。 UITableViewController是UITableViewDelegateUITableViewDataSourceNSFetchedResultsControllerDelegate

正如核心數據准則中所建議的那樣,app委托和表視圖控制器共享一個托管對象上下文,而數據加載是在另一個線程中的后台MOC上完成的。 當通過mergeChangesFromContextDidSaveNotification:調用上下文的save方法時,這些是同步的mergeChangesFromContextDidSaveNotification:

要重現,我從模擬器中刪除應用程序,運行一次,數據庫填充並正確顯示應用程序。 我停止應用程序並再次運行,沒有任何顯示。 我停止應用程序並運行第三次並正確顯示。

除了第二次啟動應用程序外,所有這些似乎都能正常工作。 第一次和第三次正常工作。 我錯過了什么?

至於我的代碼,我不知道該放什么。 讓我們從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

與我在上面的注釋中假設的相反,如果指定了sectionNameKeyPath來創建表視圖部分(我可以使用測試程序重現這一點),則會發生相同的效果。

當應用程序第一次啟動時,會發生以下情況:

  1. 創建持久性存儲文件“appname.sqlite”。
  2. 使用cacheName參數集創建表視圖的獲取結果控制器,以便創建節緩存文件。 此時,所有部分都是空的。
  3. 創建后台MOC,從資源文件中讀取一些JSON數據並將對象添加到上下文中。
  4. 后台MOC已保存。

(順便說一句,緩存文件是

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

在應用程序包中。)

第二次啟動應用程序時,獲取的結果控制器會檢查部分信息緩存是否仍然有效或是否必須重新創建。 根據文檔,它比較了持久存儲文件和節緩存文件的修改時間。

現在有趣的部分:如果(在第一次運行中)創建存儲文件(步驟1)並保存更新的上下文(步驟4) 發生在同一秒 ,那么商店文件的修改日期不會在步驟中更改 4!

因此,部分緩存文件仍被視為有效且未重新創建 由於所有部分都是空的(在步驟2中),FRC使用此緩存信息並僅顯示空部分。

后台MOC再次啟動並保存上下文。 現在,商店文件有一個新的修改日期,因此在應用程序的第三次運行中正確顯示了部分和行。

為了確認我的“理論”,我在第一次和第二次運行之間手動“觸摸”了商店文件,以強制執行更改的修改日期。 然后正確顯示所有部分和行。

(我僅在iPhone模擬器中對此進行了測試。我不知道HFS +文件系統是否通常具有1秒的修改日期分辨率,或者SQLite是否在這里做了一些特殊的事情。我稍后會嘗試對此進行調查。)

結論:如果創建存儲文件並保存修改的數據在同一秒內發生,則必要時可能不會重新生成節信息緩存文件。

我遇到了同樣的問題。 當我加載數據時,第一次加載很好。 當我重新啟動我的應用程序時,數據從表中消失。 這些是我的理由:

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

我有能力在UI中更改排序,在更改排序然后再將其更改后,問題就消失了,這肯定指出了緩存問題。

調用

[NSFetchedResultsController deleteCacheWithName:nil];

將刪除所有緩存。

我最后還選擇不緩存。 我不認為我會在我的數據庫中為這些表存儲數萬條記錄,因此我不確定緩存是否會改進任何內容。 設置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