Novarg
Novarg

Reputation: 7450

Perform fetch without freezing the UI

I'm making a small chat app. In my app I'm using NSFetchedResultsController. There are 2 tableViews, 1 for lobby and 1 for the chat room. The problem is that whenever I enter a chat room I have to wait for NSFetchedResultsController to perform fetch and load all the data before I can start typing anything. I was wondering if it's possible to preform fetch in background or somehow let the user start typing before the last messages are loaded.

So if I set that part in viewDidLoad method, my UI just freezes until all the data is loaded:

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

and if I set that piece of code into a separate method and then call that method in viewDidLoad in background

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

tableView is just not updating, so I have to go back to the first tableView and then come back to get any results.

Thank you.

Any help would be appreciated

update

I did try a few other ways to do it.

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

result: tableView doesn't show any data, need to re-open that view controller

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

result: tableView updates, but UI freezes

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

result: UI freezes, table view updates.

As I read somewhere, everything that has to do something with UI must be done on a main thread. But I still can't figure out how to do it without freezing the tableView.

Thank you for reading.

Any help is appreciated

Upvotes: 11

Views: 7985

Answers (5)

malhal
malhal

Reputation: 30582

First of all, index your entity properties properly so the queries run faster, especially if sorting. Then look at optimising your fetch request, by setting batch size, limiting the data to the bare minimum necessary. Use the SQL debugger to examine the query, and even test the queries agains the sqlite file yourself using an app like Base. If you try to use multi-threading unnecssarily for such a simple app it will only make matters worse.

FYI in your code, viewDidLoad is already on the main thread. If you are trying to delay the fetch until after the view is loaded then you can use performSelector afterDelay:0.

Upvotes: 0

Kjuly
Kjuly

Reputation: 35131

I'm not sure whether this'll work or not, but you can have a try:

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

Upvotes: 15

Ilanchezhian
Ilanchezhian

Reputation: 17478

Once the fetch is done in fetchIt method, call the reloadData method in UITableView. For more reference, see Apple documentation on UITableView

Edit:

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

For more info, visit the Apple documentation

Upvotes: 1

Caleb
Caleb

Reputation: 125007

Set the fetch request's fetchBatchSize so that you're not actually fetching all the data at once. You probably only need enough records to fill the screen, plus some more to keep initial scrolling responsive. If the data in question is, say, chat messages, I'd think that limiting the fetch to the most recent 50 or 100 messages should speed things up considerably.

Upvotes: 2

Nathan
Nathan

Reputation: 1647

Dispatch your fetch request onto a new thread:

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

    dispatch_async(YourThreadName, ^{

Your Fetch Request

    });

    dispatch_release(YourThreadName);

Upvotes: 1

Related Questions