NSFetchedResultsController 发现错误数量的对象
NSFetchedResultsController finds wrong number of objects
我看到 NSFetchRequest returns 不同数量的对象,具体取决于它是直接通过 NSManagedObjectContext 执行还是作为构建 NSFetchedResultsController 的一部分执行。
示例代码:
- (void)setupResultsController {
NSError *error = nil;
NSManagedObjectContext *ctx = [[DataManager sharedInstance] mainObjectContext];
// Create a fetch request and execute it directly
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [Song entityInManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"section" ascending:YES];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor, nameDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *debugResults = [ctx executeFetchRequest:fetchRequest error:&error];
NSLog(@"Count from context fetch: %lu", (unsigned long)debugResults.count);
// Use the request to populate a NSFetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:ctx
sectionNameKeyPath:@"section"
cacheName:@"Detail"];
[aFetchedResultsController performFetch:&error];
NSLog(@"Count from results controller fetch: %lu", (unsigned long)[[aFetchedResultsController fetchedObjects] count]);
_songResultsController = aFetchedResultsController;
}
在日志消息中执行上述结果:
2020-01-10 11:05:07.892772-0500 asb7[12985:105052] 从上下文获取计数:10
2020-01-10 11:05:07.893259-0500 asb7[12985:105052] 从结果控制器获取计数:9
两次提取的区别在于 NSFetchedResultsController 缺少最近添加的对象。一个极端奇怪的地方是,在 运行 将应用程序运行一些看似随机的次数后,计数开始一致并获取新对象。
编辑:
如果我将 nil
作为缓存名称传递,或者如果我删除第二个排序描述符,结果会变得一致。显然,这些会导致不良的行为变化,但可能是线索。
NSFetchedResultsController 似乎将过时的缓存视为有效。更改排序描述符会使缓存无效,但是更新持久存储文件 应该 会使它无效,但在这种情况下显然不会。
经过更多的试验,我有一个解释...如果不是解决方案的话。添加新对象不会更改我的 .sqlite 文件的修改日期。它更新了 .sqlite-shm 和 .sqlite-wal 但我猜在判断是否使用缓存时不会考虑这些。在终端会话中使用 touch
可使问题在下次启动时消失。
(Xcode 10.1,macOS 10.13.6,部署目标 10.3,iOS 12.1 模拟器和 10.3.2 设备)
另一次编辑:
我已经在 https://github.com/PhilKMills/CacheTest
上传了一个演示问题的压缩项目目录
我得到的是:第一个 运行,两次提取都有 3 条记录;第二个 运行、6 和 3。我认为这完全有可能取决于我的特定软件版本,但我目前无法升级。其他人的结果将是最有趣的。
注意:如果没有分配 FRC 委托,则不会出现此问题。
我怀疑问题与 FRC 缓存的使用有关,尽管我不确定为什么会这样。因此,一种解决方法是放弃使用 FRC 缓存。然而,这并不理想。
经过 OP 的进一步实验,问题似乎与与持久存储关联的 .sqlite
文件上的时间戳有关。通过推断(*),如果 FRC 检测到时间戳已更改,它会意识到其缓存可能已过时,并重建它 - 从而检测到新添加的对象。但是,因为 CoreData 默认使用 SQLite 的 "WAL journal mode"(参见 here and here),数据库更新不会写入 .sqlite
文件,而是写入一个单独的 .sqlite-wal
文件:时间戳因此,.sqlite
文件上的内容不会更改,即使数据库已更新。 FRC 继续使用它的缓存,不知道额外的对象(或者实际上其他的变化)。
因此,第二种解决方法是放弃使用 SQLite 的 WAL 日志模式。这可以通过在加载持久存储时指定所需的日志模式来实现,使用
NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}
参见 here。
(*) 事实上,这是有记录的 here:
“the controller tests the cache to determine whether its contents are still valid. The controller compares the current entity name, entity version hash, sort descriptors, and section key-path with those stored in the cache, as well as the modification date of the cached information file and the persistent store file.”
我看到 NSFetchRequest returns 不同数量的对象,具体取决于它是直接通过 NSManagedObjectContext 执行还是作为构建 NSFetchedResultsController 的一部分执行。
示例代码:
- (void)setupResultsController {
NSError *error = nil;
NSManagedObjectContext *ctx = [[DataManager sharedInstance] mainObjectContext];
// Create a fetch request and execute it directly
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [Song entityInManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"section" ascending:YES];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor, nameDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *debugResults = [ctx executeFetchRequest:fetchRequest error:&error];
NSLog(@"Count from context fetch: %lu", (unsigned long)debugResults.count);
// Use the request to populate a NSFetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:ctx
sectionNameKeyPath:@"section"
cacheName:@"Detail"];
[aFetchedResultsController performFetch:&error];
NSLog(@"Count from results controller fetch: %lu", (unsigned long)[[aFetchedResultsController fetchedObjects] count]);
_songResultsController = aFetchedResultsController;
}
在日志消息中执行上述结果:
2020-01-10 11:05:07.892772-0500 asb7[12985:105052] 从上下文获取计数:10
2020-01-10 11:05:07.893259-0500 asb7[12985:105052] 从结果控制器获取计数:9
两次提取的区别在于 NSFetchedResultsController 缺少最近添加的对象。一个极端奇怪的地方是,在 运行 将应用程序运行一些看似随机的次数后,计数开始一致并获取新对象。
编辑:
如果我将 nil
作为缓存名称传递,或者如果我删除第二个排序描述符,结果会变得一致。显然,这些会导致不良的行为变化,但可能是线索。
NSFetchedResultsController 似乎将过时的缓存视为有效。更改排序描述符会使缓存无效,但是更新持久存储文件 应该 会使它无效,但在这种情况下显然不会。
经过更多的试验,我有一个解释...如果不是解决方案的话。添加新对象不会更改我的 .sqlite 文件的修改日期。它更新了 .sqlite-shm 和 .sqlite-wal 但我猜在判断是否使用缓存时不会考虑这些。在终端会话中使用 touch
可使问题在下次启动时消失。
(Xcode 10.1,macOS 10.13.6,部署目标 10.3,iOS 12.1 模拟器和 10.3.2 设备)
另一次编辑:
我已经在 https://github.com/PhilKMills/CacheTest
上传了一个演示问题的压缩项目目录我得到的是:第一个 运行,两次提取都有 3 条记录;第二个 运行、6 和 3。我认为这完全有可能取决于我的特定软件版本,但我目前无法升级。其他人的结果将是最有趣的。
注意:如果没有分配 FRC 委托,则不会出现此问题。
我怀疑问题与 FRC 缓存的使用有关,尽管我不确定为什么会这样。因此,一种解决方法是放弃使用 FRC 缓存。然而,这并不理想。
经过 OP 的进一步实验,问题似乎与与持久存储关联的 .sqlite
文件上的时间戳有关。通过推断(*),如果 FRC 检测到时间戳已更改,它会意识到其缓存可能已过时,并重建它 - 从而检测到新添加的对象。但是,因为 CoreData 默认使用 SQLite 的 "WAL journal mode"(参见 here and here),数据库更新不会写入 .sqlite
文件,而是写入一个单独的 .sqlite-wal
文件:时间戳因此,.sqlite
文件上的内容不会更改,即使数据库已更新。 FRC 继续使用它的缓存,不知道额外的对象(或者实际上其他的变化)。
因此,第二种解决方法是放弃使用 SQLite 的 WAL 日志模式。这可以通过在加载持久存储时指定所需的日志模式来实现,使用
NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}
参见 here。
(*) 事实上,这是有记录的 here:
“the controller tests the cache to determine whether its contents are still valid. The controller compares the current entity name, entity version hash, sort descriptors, and section key-path with those stored in the cache, as well as the modification date of the cached information file and the persistent store file.”