NSFetchedResultsController 缓存再次不匹配
这是另一种情况,在 NSFetchedResultController
上调用 performFetch
会中断并出现异常:
致命错误:节信息的持久缓存与当前配置不匹配。您非法改变了 NSFetchedResultsController 的获取请求、其谓词或其排序描述符,而没有禁用缓存或使用 +deleteCacheWithName:
除非我没有这样做。要理解这个问题,请查看谓词:
谓词:(collection.identifier == ";root" AND doc.status == 0);
现在看看不匹配的缓存内容:
cached objects were: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)"
)
fetched objects are: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00004.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00004.pdf' in set '/' (#2147483647)"
)
确实,有两个新的匹配对象。原因是两个新对象(PL00004.jpeg
和 PL00004.pdf
)之前的 status
属性值为 <> 0,我在代码中将它们的 status
更改回 0,因为它们开始满足某些外部条件(不是由用户操作触发)。
为了完整起见,这里是触发异常的代码:
- (void) reloadData
{
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // I have actually better error handling
}
[_gridView reloadData];
}
另请注意:
- 没有其他
NSFetchedResultController
使用同名的缓存 _gridView
不是UITableView
>,尽管它也显示实体的集合。这个细节不应该是相关的。- 没有任何一个 NSFetchedResultController 委托方法被调用,尤其是当我将两个对象的状态更改为 0 时,
- 如果我在
performFetch:
之前清空缓存,一切都会正常运行(或者没有缓存)。 - 在定期检查上面提到的外部条件结束时,我在控制器上调用此
reloadData
方法。 NSFetchedResultController
是使用nil
sectionNameKeyPath
创建的。- 正如所解释的,我从不改变 fetchedResultsController :不改变它的谓词,不改变它的获取请求,不改变它的排序顺序,什么都没有。
我认为我不应该仅仅因为新对象现在满足谓词而得到这个异常。删除缓存可以避免异常,但只要我不明白为什么应该这样做,我就不认为这是一个修复。
知道我想念这里什么吗?
编辑:这里是请求的信息:
设置 NSFetchedResultController
的代码。当用户选择一个集合时,将调用:
- (void) displayCollection:(Collection *)aSet
{
self.currentCollection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSString *collectionIdentifier = @";root"; // no selection is like "select root collection";
if (aSet) {
collectionIdentifier = aSet.identifier;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", collectionIdentifier, upToDate];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:cacheName];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // whatever
}
[_gridView reloadData];
}
fetchedResultsController
已初始化:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"DocInCollection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", @";root", upToDate];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"position" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
doc.status
更新时调用的代码:
- (void) setUpToDate
{
self.status = [NSNumber numberWithInt:upToDate];
NSError *error = nil;
[[self managedObjectContext] save:&error];
if (error) { } // whatever
[[self managedObjectContext] processPendingChanges];
}
cacheName
是一个全局常量 NSString
,upToDate
是一个枚举常量 (0)。
最后,由于应用程序使用了其他实体,该模型有点复杂。这是一个简化版本:
模型 http://emberapp.com/jdmuys /images/untitled-1/sizes/m.png
This is another case where calling performFetch
on an NSFetchedResultController
breaks with an exception saying:
FATAL ERROR: The persistent cache of section information does not match the current configuration. You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName:
Except I haven't done any of this. To understand the problem, look at the predicate:
predicate: (collection.identifier == ";root" AND doc.status == 0);
Now look at the mismatched cache contents:
cached objects were: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)"
)
fetched objects are: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00004.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00004.pdf' in set '/' (#2147483647)"
)
Indeed, there are two new matching objects. The reasons is that the two new objects (PL00004.jpeg
and PL00004.pdf
) had previously their status
property to a value <>0 and I changed their status
back to 0 in code because they started to satisfy some external condition (not triggered by a user action).
For completeness, here is the code that triggers the exception:
- (void) reloadData
{
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // I have actually better error handling
}
[_gridView reloadData];
}
also note:
- no other
NSFetchedResultController
uses a cache with the same name _gridView
is not aUITableView
, though it also displays a collection of entities. This detail should not be relevant.- none of the
NSFetchedResultController
delegate methods are ever called, esp not when I change the status of my two objects to 0 - everything runs OK if I empty the cache before
performFetch:
(or with no caching). - I call this
reloadData
method on my controller at the end of my periodical check for that external condition I mentioned above. - The
NSFetchedResultController
is created with annil
sectionNameKeyPath
. - As explained I never change
fetchedResultsController
: not its predicate, not its fetch request, not its sort order, nothing.
I don't think I should get this exception simply because new objects now satisfy the predicate. Deleting the cache avoids the exception, but as long as I don't understand why I should do that, I don't consider that as a fix.
Any idea what I miss here?
Edit: here is the requested information:
The code to set up the NSFetchedResultController
. When the user chooses a collection, this is called:
- (void) displayCollection:(Collection *)aSet
{
self.currentCollection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSString *collectionIdentifier = @";root"; // no selection is like "select root collection";
if (aSet) {
collectionIdentifier = aSet.identifier;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", collectionIdentifier, upToDate];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:cacheName];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // whatever
}
[_gridView reloadData];
}
fetchedResultsController
was initialized thus:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"DocInCollection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", @";root", upToDate];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"position" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
The code that is called when doc.status
is updated:
- (void) setUpToDate
{
self.status = [NSNumber numberWithInt:upToDate];
NSError *error = nil;
[[self managedObjectContext] save:&error];
if (error) { } // whatever
[[self managedObjectContext] processPendingChanges];
}
cacheName
is a global const NSString
, upToDate
is an enum constant (0).
Finally the model is a bit complex because of other entities that are used by the application. Here is a simplified version:
model http://emberapp.com/jdmuys/images/untitled-1/sizes/m.png
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我会查看您的
sectionNameKeyPath
及其与正在突变的属性的关系(如果有)。从错误消息看来,您的部分正在以控制器不喜欢的方式发生变化。不确定它是否相关,但我会对任何其中包含未转义分号的语句感到紧张。
";root"
的谓词让我很痒。当然,它的任何问题都应该在编译时出现,但仍然......更新:
出于病态的好奇心,我使用上面的方法将测试数据模型和一些代码组合在一起。以下内容甚至不会编译:
将谓词更改为:
...编译并正常工作。我建议您将
";root"
更改为';root'
至少用于故障排除。I would look at your
sectionNameKeyPath
and it's relationship, if any, to the attribute being mutated. It would seem from the error message that your sections are changing in a way the controller does not like.Not sure if it is related at all but I would be nervous of any statement that has an unescaped semicolon in it. That predicate bit of
";root"
gives me an itch. Of course, any problems with it should show up at compile time but still...Update:
Out of morbid curiosity I banged together a test data model and some code using the above. The following will not even compile:
Changing the predicate to:
... compiles and works properly. I suggest you change the
";root"
to';root'
at least for troubleshooting.