问题 在不冻结UI的情况下执行提取


我正在制作一个小型聊天应用。在我的应用程序中,我正在使用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,然后回来获取任何结果。

谢谢。

任何帮助,将不胜感激

更新

我确实尝试了其他一些方法来做到这一点。

方式1:

-(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的情况下做到这一点。

谢谢你的阅读。

任何帮助表示赞赏


4800
2018-01-11 12:37


起源

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


答案:


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

- (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
}

13
2018-01-13 09:21



我用它来解决类似问题。它按预期工作,除了当表重新加载它是BLAM时,所有记录都会突然出现。我想我第一次看到它时就感到很畏缩,这让我感到很惊讶。任何人都可以想到一种方法,让它们更有机地流动到位而不会吓到用户吗? - Dean Davids
@DeanDavids你可以创建一个方法(例如 reloadDataAnimated:(BOOL)animated)用淡化动画和放 [self.tableView reloadData]; 进入这种方法。然后用 dispatch_async(dispatch_get_main_queue(), ^{ [self reloadDataAnimated:YES]; }); 我没试过,只是一个建议。 :) - Kjuly
Downvoted。这是一个古老的答案,但这是完全错误的。这将在NSManagedObjectContext的Threading问题中返回。打开多线程日志,您将获得CoreData` + [NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor] :.


将您的获取请求发送到新线程:

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

    dispatch_async(YourThreadName, ^{

Your Fetch Request

    });

    dispatch_release(YourThreadName);

1
2018-01-11 16:48



谢谢你的答案,但除非我重新打开那个视图控制器,否则tableView不会重新加载 - Novarg


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


1
2018-01-11 18:22



谢谢你的答案,但它已经只拿到了50 - Novarg


一旦完成提取 fetchIt 方法,打电话给 reloadData 方法 UITableView。有关更多参考,请参阅Apple文档 UITableView的

编辑:

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

有关更多信息,请访问 Apple文档


1
2018-01-11 12:51



谢谢你的答案,但它仍然没有显示任何东西 - Novarg
你能告诉我你在哪里重装桌子吗? - Ilanchezhian
我把它作为“fetchIt”方法的最后一行。整个方法看起来像这样: - (void)fetchIt {NSError * _error; if(![self.fetchedResultsController performFetch:&_ error]){NSLog(@“Unresolved error%@,%@”,_ error,[_ error userInfo]); } [self.tableView reloadData]; } - Novarg
实行 NSFetchedResultsControllerDelegate 并按照编辑部分的答案实现方法。 - Ilanchezhian


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

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


0
2017-09-10 11:52