在阅读了数十个类似的问题后,我想以这样的说法开始:“我确实设置了 NSFetchedResultsController 的委托,但它不起作用。”。
所以我有一个简单的 TableViewController,其单元格填充有 NSFetchedResultsController。这是 FRC 初始化代码:
@property (strong, nonatomic) NSFetchedResultsController *frc;
...
- (NSFetchedResultsController *)frc
{
if (!_frc)
{
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product];
request.sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:@"product_group.product_group_name" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:f_product_name ascending:YES],
nil];
_frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[DTGGlobalSettings sharedInstance].moc sectionNameKeyPath:@"product_group.product_group_name" cacheName:nil];
_frc.delegate = self;
[_frc performFetch:&error];
if (error)
{
NSLog(@"Error while fetching products: %@!", error.userInfo);
}
}
return _frc;
}
我还使用一些调试标签实现了 NSFetchedResultsController 委托方法:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
NSLog(@"controllerWillChangeContent");
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
NSLog(@"didChangeObject - insert");
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
NSLog(@"didChangeObject - delete");
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
NSLog(@"didChangeObject - update");
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
NSLog(@"didChangeObject - move");
[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:
NSLog(@"didChangeSection - insert");
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
NSLog(@"didChangeSection - delete");
[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.
NSLog(@"controllerDidChangeContent");
[self.tableView endUpdates];
}
在我的导航栏中,我有刷新按钮,其功能是启动产品目录方法,该方法执行以下操作:
1.从远程MySQL服务器获取产品
2. 如果具有 ID 的产品不存在 - 创建它并填写所有字段
3. 如果存在,则根据获取的值更新所有字段
在第 2 行和第 3 行之后,商店被保存。
我对应用程序中的所有 CoreData 操作使用单个 NSManagedObjectContext。
当我第一次启动应用程序时,TVC 是空的,因为尚未获取任何产品。当我按“刷新”时,将获取新产品并将其插入到 ManagedObjectContext 中,但 NSManagedObjectContext 没有看到/听到任何更改:没有调用委托方法,因此没有新行添加到 TVC。
当我重新启动应用程序时,新产品会毫无问题地提取到 TVC 中。
如果我在有一些产品时再次按刷新按钮,则在短暂延迟后它们都会消失。一些调试向我表明,如果在更新实体的字段(实际上值相同)并保存商店后发生。这次的委托方法就像一个魅力,对于每个更新和保存的实体控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath 都通过 forChangeType = NSFetchedResultsChangeDelete 调用。
问题#1:为什么 NSFetchedResultsController 在不重新启动应用程序的情况下看不到插入的实体?
问题#2:为什么 NSFetchedResultsController 将已获取的实体标记为“不存在”(?)并在存储保存后将它们从 TVC 中删除?
我将不胜感激任何帮助和想法,上周整个星期都在与这个问题作斗争;(
UPD1(对于乔迪):以下是一些细节。我使用自定义类 DTGGlobalSettings 在应用程序中共享一些变量。这就是我初始化其sharedInstance属性的方式:
+(DTGGlobalSettings *)sharedInstance
{
static DTGGlobalSettings *myInstance = nil;
if (nil == myInstance)
{
myInstance = [[[self class] alloc] init];
// set values here
// try to acces MOC to init CoreData
[myInstance moc];
myInstance.baseURL = @"http://local.app/";
}
return myInstance;
}
为了初始化 CoreData 堆栈,我使用了 Apple 文档中的示例,因为出于某种原因,我在创建新项目时没有看到“使用 CoreData”复选框。我确信我以前在 Xcode 中见过它,但现在我没有;( (Xcode 4.4.1):
#pragma mark Core Data stack
- (NSManagedObjectContext *) moc {
if (_moc != nil) {
return _moc;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_moc = [[NSManagedObjectContext alloc] init];
[_moc setPersistentStoreCoordinator: coordinator];
}
return _moc;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
storeUrl = [storeUrl URLByAppendingPathComponent:DOC_NAME];
NSError *error;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
NSLog(@"Error adding a store to the coordinator: %@, %@", error, error.userInfo);
}
return _persistentStoreCoordinator;
}
-(void)saveDataStore
{
NSError *error;
if (![self.moc save:&error]) NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
然后,DTGGlobalSettings 的“moc”(ManagedObjectContext)属性将在应用程序中需要访问 CoreData 的任何地方使用。
现在进入第二部分,更新数据库。我从远程 Web 服务器获取一些 JSON 格式的新实体,遍历生成的字典并为请求结果中找到的每个实体调用 createFromDictionary 方法。这是代码:
+(Product *)createFromDictionary:(NSDictionary *)entityData inContext:(NSManagedObjectContext *)moc performUpdate:(BOOL)update
{
Product *result;
if (update)
{
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product];
request.predicate = [NSPredicate predicateWithFormat:@"%K = %@", f_product_id, [entityData objectForKey:f_product_id]];
result = [[DTGGlobalSettings sharedInstance].moc executeFetchRequest:request error:nil].lastObject;
if (!result)
{
NSLog(@"Error updating Product ID %@! Cannot fetch entity.", [entityData objectForKey:f_product_id]);
return nil;
}
} else
{
result = [NSEntityDescription insertNewObjectForEntityForName:e_product inManagedObjectContext:[DTGGlobalSettings sharedInstance].moc];
}
result.product_id = [[DTGGlobalSettings sharedInstance].numFormatter numberFromString:[entityData objectForKey:f_product_id]];
result.product_image = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[DTGGlobalSettings sharedInstance].baseURL stringByAppendingString:[entityData objectForKey:f_product_image]]]];
result.product_name = [entityData objectForKey:f_product_name];
return result;
}
当我用完所连接的实体时,我调用 [[DTGGlobalSettings sharedInstance] saveDataStore] (见上文)。此时,TVC 中的现有行(如果有)消失。如果我添加一些新实体,在保存时什么也不会发生,即 TVC 不会更新其行。