6

所以我想要达到的目标

是一个同步过程,应该使用 AFNetworking 和 Magical Record 在后台完成,但是当连接到 NSFetchedResultsController 的视图控制器当前打开或已打开(但弹出)时会导致永久挂起。

该应用程序会在用户第一次打开手机时进行同步,然后通过 Magical Record 框架始终使用 Core Data 持久性存储中的数据。然后当用户想要确保那里的数据是最新版本时,他们进入设置并单击“重新同步”,这会导致执行以下代码:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
    [[CoreDataOperator sharedOperator] commenceSync:self];
});

这将使用单例 CoreDataOperator(NSObject 的子类——也许它应该是 NSOperation?)启动同步过程,它会触发以下代码:

[[ApiClient sharedClient] getDataRequest];

然后在单例 AFHTTPClient 子类中触发这个坏男孩:

[[ApiClient sharedClient] postPath:url parameters:dict
 success:^(AFHTTPRequestOperation *operation, id responseObject) {
 [request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
 [request.sender performSelector:request.failed withObject:response];
}
 ];

这实际上是说:AFHTTPClient 发布此信息,当它成功时,将信息传递回提供的选择器(我知道这是通用的,但请求不是问题)

现在,AFNetworking 被编码为在主线程上调用所有完成选择器(在本例中为成功和失败);所以为了防止阻塞主线程,处理响应并准备保存的代码被发送回后台线程:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    id responseData;
    [self clearAndSaveGetData:responseData];
});

然后使用 Magical Record 框架调用导致保存的函数(仍在后台线程中):

NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];

[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];

我之所以选择saveNestedContexts是因为因为我在后台工作,所以我希望它一直推到默认上下文,我假设它是父上下文?(但到目前为止这还不是问题)。

现在,这些数据可以变成成千上万行,所以我使用 NSFetchedResultsController 来安全有效地访问这些数据,并且它们在与设置或主页不同的视图控制器中使用。

以下是三种情况:

  1. 包含 FRC 的 ViewController 尚未被访问(不可见且以前不可见) - 后台同步工作完美,由于节省堆栈而减去了一点延迟。
  2. 包含 FRC 的 ViewController 已被访问并且当前可见-由于 FRC 接收上下文更新,后台同步进程挂起。
  3. 包含 FRC 的 ViewController 先前已被访问,但当前不可见(使用以下代码弹出 VC :)并且 FRC 在[self.navigationController popViewControllerAnimated:YES];ViewDidUnload中设置为 nil -由于似乎是 FRC,后台同步进程挂起接收上下文更新(就像案例 2 一样)。

[PS:挂了几分钟后,我杀死了调试器,它在下面的代码上给了我一个 SIGKILL,这就是为什么我假设 FRC 正在接收导致它挂起的上下文更新:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
    UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
    [tableView endUpdates]; <---- SIGKILL
}

其他需要注意的重要信息:

  • 我正在使用 2 个单独的 FRC,一个用于普通 ALL 数据,一个用于搜索
  • 我正在为 FRC 和单独的搜索 FRC 使用缓存,该缓存在适当的时间被清除(在此上下文更新内容之前)
  • FRC 正在主线程中获取(当有数千行数据时,这确实会导致轻微的挂起),我一直在研究在后台获取,但是目前还没有实现。

问题:

  1. 为什么会发生这种挂起,VC 可见或已弹出?
  2. 如何使 FRC 不监听保存,而是使用它所拥有的内容,直到保存完成,然后刷新数据(除非这是已经发生并导致挂起的情况)?
  3. 实现后台获取(因为使用 FRC 打开 VC 以访问数千行数据时的延迟会产生明显的延迟,即使使用缓存和减少的谓词/节标题 - 在 1 到 4 秒之间)是否可行?太复杂了?怎么做到呢?

谢谢,希望我的问题足够详细。

4

1 回答 1

1

我知道这个问题很老,但由于这是 MagicalRecord 的常见问题,也许以下内容会对某人有所帮助。

该问题是由fetchRequestFRC 和保存操作相互死锁引起的。以下为我解决了它:

更新到最新的 MagicalRecord 版本(撰写本文时为 2.1)。

然后使用以下命令进行所有背景保存:

MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
  // create new objects, update existing objects. make sure you're only 
  // accessing objects inside localContext 
  [myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
  // this gets called in the main queue. safe to update UI.
}];
于 2013-05-22T18:20:42.073 回答