【问题标题】:Perform fetch without freezing the UI在不冻结 UI 的情况下执行 fetch
【发布时间】:2012-02-07 19:34:36
【问题描述】:

我正在制作一个小型聊天应用程序。在我的应用程序中,我使用的是 NSFetchedResultsController。有 2 个 tableViews,1 个用于大厅,1 个用于聊天室。问题是,每当我进入聊天室时,我都必须等待 NSFetchedResultsController 执行获取并加载所有数据,然后才能开始输入任何内容。我想知道是否可以在后台执行 fetch 或以某种方式让用户在加载最后一条消息之前开始输入。

因此,如果我在 viewDidLoad 方法中设置该部分,我的 UI 就会冻结,直到所有数据都加载完毕:

NSError *_error;
if (![self.fetchedResultsController performFetch:&_error]) {
    NSLog(@"Unresolved error %@, %@", _error, [_error userInfo]);
}

如果我将那段代码设置为单独的方法,然后在后台的 viewDidLoad 中调用该方法

[self performSelectorInBackground:@selector(fetchIt) withObject:nil];

tableView 只是没有更新,所以我必须回到第一个 tableView,然后再回来获得任何结果。

谢谢。

任何帮助将不胜感激

更新

我确实尝试了其他几种方法。

方式一:

-(void)reloadTheTable
{
  [self.tableView reloadData];
}

-(void)fetchIt
{
  NSError *_error;
  if (![self.fetchedResultsController performFetch:&_error]) {
    NSLog(@"Unresolved error %@, %@", _error, [_error userInfo]);
  }
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
  [self performSelectorOnMainThread:@selector(reloadTheTable) withObject:nil waitUntilDone:NO];
  [pool release];
}

- (void)viewDidLoad {
  //some code
  [self performSelectorInBackground:@selector(fetchIt) withObject:nil];
  //some other code
}

结果:tableView 没有显示任何数据,需要重新打开那个视图控制器

方式2:

- (void)viewDidLoad {
  //some code
  dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    NSError *_error;
    if (![self.fetchedResultsController performFetch:&_error]) {
        NSLog(@"Unresolved error %@, %@", _error, [_error userInfo]);
    }
  });
  //some other code
}

结果:tableView 更新,但 UI 冻结

方式3:

-(void)fetchIt
{
  NSError *_error;
  if (![self.fetchedResultsController performFetch:&_error]) {
    NSLog(@"Unresolved error %@, %@", _error, [_error userInfo]);
  }
}

- (void)viewDidLoad {
  //some code
  [self performSelectorOnMainThread:@selector(fetchIt) withObject:nil waitUntilDone:NO];
  //some other code
}

结果:UI 冻结,表格视图更新。

正如我在某处读到的,所有与 UI 相关的事情都必须在主线程上完成。但我仍然不知道如何在不冻结 tableView 的情况下做到这一点。

感谢您的阅读。

感谢任何帮助

【问题讨论】:

  • 我想你在找THIS。
  • @Kjuly 谢谢,但它仍然有点反应迟钝。使用 dispatch_async(dispatch_get_main_queue(), ^{ ... });
  • 不要使用dispatch_get_main_queue这也会阻塞你的应用,因为UI在主线程中运行,你需要在其他线程中获取数据。
  • @Kjuly 我确实尝试了 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) 和 dispatch_queue_create(nil, nil),但是 tableView 只是没有更新
  • @Novary 更新表视图?试试[tableView reloadData];。 :)

标签: iphone ios user-interface core-data nsfetchedresultscontroller


【解决方案1】:

我不确定这是否有效,但您可以尝试一下:

- (void)methodThatloadYourData {
  NSError *_error;
  if (![self.fetchedResultsController performFetch:&_error]) {
    NSLog(@"Unresolved error %@, %@", _error, [_error userInfo]);
  }
}

- (void)viewDidLoad {
  //some code

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
                 ^{
                   [self performSelector:@selector(methodThatloadYourData)];
                   dispatch_async(dispatch_get_main_queue(),
                                    ^{
                                      [self.tableView reloadData];
                                    });
                 });

  //some other code
}

【讨论】:

  • 我用这个来解决类似的问题。它按预期工作,除了当表重新加载它是 BLAM 时,所有记录都会突然出现。我想我第一次看到这个时吓了一跳,这让我很惊讶。谁能想出一种方法让它们更自然地流动而不吓到用户?
  • @DeanDavids 你可以创建一个带有淡入淡出动画的方法(例如reloadDataAnimated:(BOOL)animated)并将[self.tableView reloadData]; 放入这个方法中。然后用dispatch_async(dispatch_get_main_queue(), ^{ [self reloadDataAnimated:YES]; });我没试过,只是一个建议。 :)
  • 投反对票。这是一个旧答案,但这是完全错误的。这将返回 NSManagedObjectContext 的线程问题。打开Multithreading log,你会得到CoreData`+[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]:.
【解决方案2】:

设置获取请求的fetchBatchSize,这样您实际上就不会一次获取所有数据。您可能只需要足够的记录来填满屏幕,再加上一些记录来保持初始滚动响应。如果有问题的数据是聊天消息,我认为将获取限制为最近的 50 或 100 条消息应该会大大加快速度。

【讨论】:

  • 感谢您的回答,但它已经只获取最后 50 个
【解决方案3】:

将您的 fetch 请求分派到新线程:

dispatch_queue_t coreDataThread = dispatch_queue_create("com.YourApp.YourThreadName", DISPATCH_QUEUE_SERIAL);

    dispatch_async(YourThreadName, ^{

Your Fetch Request

    });

    dispatch_release(YourThreadName);

【讨论】:

  • 感谢您的回答,但除非我重新打开该视图控制器,否则 tableView 不会重新加载
【解决方案4】:

在fetchIt 方法中完成提取后,在UITableView 中调用reloadData 方法。如需更多参考,请参阅 Apple 文档 UITableView

编辑:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView reloadData];
}

欲了解更多信息,请访问Apple documentation

【讨论】:

  • 你能告诉我你从哪里重新加载表吗?
  • 我把它作为“fetchIt”方法的最后一行。整个方法如下所示: -(void)fetchIt{ NSError *_error; if (![self.fetchedResultsController performFetch:&_error]) { NSLog(@"未解决的错误 %@, %@", _error, [_error userInfo]); } [self.tableView reloadData]; }
  • 实现NSFetchedResultsControllerDelegate 并按照答案的已编辑部分实现该方法。
【解决方案5】:

首先,正确索引您的实体属性,以便查询运行得更快,尤其是在排序时。然后通过设置批量大小来优化您的获取请求,将数据限制在必要的最低限度。使用 SQL 调试器检查查询,甚至使用 Base 之类的应用程序自己针对 sqlite 文件测试查询。如果您尝试对这样一个简单的应用程序不必要地使用多线程,那只会让事情变得更糟。

在您的代码中仅供参考,viewDidLoad 已经在主线程上。如果您尝试将获取延迟到加载视图之后,则可以使用 performSelector afterDelay:0。

【讨论】:

    猜你喜欢
    • 2012-04-21
    • 2016-09-15
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 2021-02-26
    相关资源
    最近更新 更多