Sidharth J Dev
Sidharth J Dev

Reputation: 987

Stop execution of a GCD, when the ViewController is popped

I have a -[tableView reloadData] method in my app and to make the execution faster, I have called it in a GCD in the following method.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

    [tableView reloadData];
});    

But when I pop my viewController, the app crashes with this message [SecondViewController numberOfSectionsInTableView:]: message sent to deallocated instance 0x7fe376206e10. I am assuming that the [reloadData] is still being executed after I exit from that ViewController. How can I stop its execution? Should I make this into an NSOperation? If so, how will I do it?

Upvotes: 7

Views: 792

Answers (3)

Amin Negm-Awad
Amin Negm-Awad

Reputation: 16650

As an option you can weakly retain the data source and check for it:

__weak __typeof__(self) dataSource = self; // or whatever it is

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  if (dataSource!=nil)    
  {
    [weakTable reloadData];
  }
});

There is still a very unlikely crash, if you hold the data source, set a new one to the table view and remove that one again, so it is deallocated.

Upvotes: 0

FormigaNinja
FormigaNinja

Reputation: 1571

Unfortunately, you can't stop the GCD execution, but there is another ways to fix this bug. As the main question in this thread is about stopping the execution, I will post a solution based on what you ask, using NSOperation.

1- Create an NSOperationQueue

NSOperationQueue *_myQueue;
_myQueue = [NSOperationQueue new];
_myQueue.name = @"com.my.queue";

2- Reload your table from your queue. (I will use blocks ok?)

[_myQueue addOperationWithBlock:^{

    //your really expensive function
    //and your table reload call
    [tableView reloadData];
}];

3- Now you can cancel the execution using

//maybe you will want to do this on viewDidDisappear
[_myQueue cancelAllOperations];

Update:

Ooops, I saw that you are delaying the table reload call, but NSOperation do not have a delay mechanism. To resolve this you can simulate a delay using

[NSThread sleepForTimeInterval:1.5];

before calling [tableView reloadData]; within addOperationWithBlock: or continue using GCD as you are doing right now, and changing the tableView's reference to weak to avoid the block retaining your tableView object, like this:

__weak __typeof__(tableView) weakTable = tableView;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

    //Now, if your table object was released at this point, the reloadData
    //will be ignored
    [weakTable reloadData];
});

Hope it helps...

Upvotes: -1

Lance
Lance

Reputation: 9012

There are a couple of problems with your code. Here are the sequence of events leading up to the crash

1) The block captures the tableView and keeps it alive.

2) Your view controller is then deallocated by the pop,

3) The block executes and the tableView calls through to it's data source (your view controller) which is now deallocated.

You can solve this by stoping either #1 or #3 above. I would suggest #1. (I'm assuming ARC here)

__weak UITableView *weakTableView = tableView;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [weakTableView reloadData];
});

If this doesn't work, something else is likely keeping the tableView alive. You should investigate what exactly is doing that but you can also fix the crash by preventing #3 from your view controllers dealloc method:

- (void)dealloc {
    self.tableView.dataSource = nil;
    self.tableView.delegate = nil;
}

Upvotes: 3

Related Questions