Safequarding against crashes while swapping out NSFetchedResultsController for UITableView - ios

I'm trying to swap the NSFetchedResultsController used for a UITableView, but it takes a few lines of code and I'm worried that another thread could try to access it halfway through and cause the app to crash. Here's the code I'm currently using:
The fetched results controller is created in the getter if it does not already exist, so the next time the table view tries to access it, it will be regenerated.
- (void)regenerateFetchedResultsController
{
self.fetchedResultsController = nil;
[self loadData]; // Loads data into the fetched results controller.
[self.tableView reloadData];
}
I'd guess that if something tried to access the FOC in the middle of this method things would go wrong, so I thought I would use UITableView's beginUpdates and endUpdates methods and the start and end of the method.
However, it seems you can't have reloadData between beginUpdates and endUpdates, so I don't really know what else to try.

All table view data source methods (numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath, ...) are only called on the main thread. Therefore, if you run your regenerateFetchedResultsController also on the main thread, it will not happen that any other method accesses the FRC in that time.
It may depend on what [self loadData] exactly does, but setting the fetched results controller to nil and letting it "auto-recreate" by calling reloadData is a standard pattern.

Related

NSFetchedResultsController "cannot access fetched objects before -performFetch:" crash when calling objectAtIndexPath:

I my crashlogs, I found an interesting crash which I can't figure out why it happened. Neither I'm able to replicate it.
It crashed when on the following line inside my implementation of tableView:didSelectRowAtIndexPath: method, when the user tapped a cell:
fetchedResultsController?.objectAtIndexPath(indexPath) as! SearchResultEntity
The crash:
Fatal Exception: NSInvalidArgumentException
cannot access fetched objects before -performFetch:
The thing is that performFetch: is being called in view controller's viewDidLoad method. Therefore, it's not possible that performFetch: was not called before tableView:didSelectRowAtIndexPath: delegate method was called. Or is it?
I'm using pretty common setup with NSFetchedResultController which is displaying results from a database. The records in the database are frequently changed as old records are being deleted and new inserted.
One thing that comes to my mind is that the user could tap the cell while its related object was deleted from CoreData and the table view was animating deletion changes. Is that possible?
If that's the cause, is there any way how to avoid it?
Yes, it can happen that you may access a delete object was from a fetchedResultsController, but it is very rare and it is not what happened here. In general the NSFetchedResultController's job is to be in sync with core data. But there can be a very small point in time just as the update come in but before the fetchedResultsController has a chance to update where accessing the fetchedObject can lead to a crash. But it would not cause this crash of a performFetch not being called.
Look in you logs (or create logs if you don't have) for the performFetch failing with an error. This could happen if the user was very low on disk space.
Check if the fetchedResultsController was replaced by a different fetchedResultsController that perform fetched wasn't called on.
Review if you have any if statements in your viewDidLoad that could cause performFetch to not be called. Even if those statements are expected to always be true there can be situations where they are not.
If you set self.view = nil in your viewController at any point this could cause viewDidLoad to be called again which may lead to unexpected behavior in your app.
I know this is an old question, but I think the same problem struck me today:
I have a setup with a UITableviewcontroller whose UISearchView can be used to search for entities. This is done by using an NSFetchResultsController to query Core Data in a background thread with an updated FetchRequest predicate on each keystroke and reloading the tableview after the successful request.
After having searched and clicked on an entry, upon returning to the list and rapidly clicking on any item again, I get the exact same error. This is due to the fact that apparently the UISearchView triggers a call to updateSearchResults(for searchController) on viewDidAppear, leading to a performFetch of the NSFetchedResultsController. In the brief timeframe between the start of the fetch and the invisible reload of the (unchanged) rows in the tableview, clicking on them leads to the mentioned error.

UICollectionView Reloading Data

A question regarding reloading data in your UICollectionView.
When using reloadData it only does this once any scrolling has finished. Then there is a small pause where the collection view freezes and cannot be interacted with until the new data is loaded in. How can I get around this?
For example, I would have a view comprised of maybe 10/20/30 items, each with an image and label. I then update my dictionary with new images and strings and then reload the view to add these new items.
Many thanks.
EDIT:
On closer inspection, its not the reloading that causes the pause, its some other code, where I fetch some data. Why might this freeze my view though when i am not even reloading it to make changes to it?
I am fetching this data from -(void)scrollViewDidScroll:(UIScrollView *)scrollView
EDIT 2:
Ok here is some basic code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self myMethodWithCompletionBlock:^(NSMutableDictionary *results, NSString *nextPageToken) {
//No code in completion block is ever called when the method is called in the dispatch block! Regardless of what it is.
}
});

When is tableView:numberOfRowsInSection: called in UITableView?

tableView:numberOfRowsInSection is sent to the delegate of a UITableView to find out how many rows it needs to have in a given section.
My question is, when and how often is this method called?
The method is called very first time the tableview is getting loaded and if you are more interested in the delegates then put a breakpoint and check when and where which delegate is called and how many times.
Below are the instances when that function will get called,
For the first time when table is loaded
the time you reload the table data
the time you add/update/delete your row or sections dynamically.
The method - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section is a protocol method of the UITableViewDataSource - protocol. It will be called
the very first time your table view is loaded based on that you have set the dataSource properly, e.g.
self.yourTableView.dataSource = self;
If you are interested in updating your table again at a later time you can call
[self.yourTableView reloadData];
in order to reload the entire table. If you are only interested in reloading a part of your table you can do something similar to
NSIndexSet *reloadSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfSectionsInTableView:self.yourTableView])];
[self.yourTableView reloadSections:reloadSet withRowAnimation:UITableViewRowAnimationAutomatic];
Hope it helps!
My question is, when and how often is this method called?
Short Answer : When your UITableView needs to update something.
Long Answer : Delegates Methods generally called themselves however it may be called multiple times when your UITableView needs to update something. By default, it's called very first time the tableview is getting loaded or updated (reloaded).
It depends on how often user will scroll UITable view to section and how many sections there are. This value, which is returned by this function and is casched. Method will need be revoked if you will update content of table view (filtering results, or updating data via reloadData).
Best thing for you will be to add logging to this function and check this yourself.

Where to load my array?

- (void)viewWillAppear:(BOOL)animated
{
goals = [[NSArray alloc] init];
goals = [[self.operation valueForKeyPath:#"goal.goalNaam"] allObjects] ;
[self.tableView reloadData];
I'm loading my elements for my tableview in viewwillappear, as you can see in the code above. When the user adds something to the table (adding happens in a different view), it's added to Core Data and the array is loaded when the tableview is again appearing (after the dismissed "add-an-element-view"), now I want to delete something from the table. But the problem is that we're now staying in the same view (the tableview), so my array is not reloading (cause the viewwillappear is not executed). Anybody an idea how to solve this ?
You can call "[self viewWillAppear:YES];" after delete the data from Core data, So the datas are reloaded from the same view (Tableview)
You load your data in cellForRowAtIndexPath. This is called as cells come into view, and only cells in view (or almost in view) are loaded.
To force something to be reloaded, use reloadRowsAtIndexPaths or one of the other reload... methods.
Check out NSFetchedResultsControllerDelegate . You can set your VC as the fetch result delegate, and be notified when the data structure changes.

Reload UITableView with a different number of rows

I've got a UITableView set up and working, and now I have a little issue. I need to reload the tableView's data whenever the view loads, and one thing which I can't get working is attempting to reload the data, when there was no data in the table view to begin with.
Basically the list is of variable length, and the contents are loaded from a file, what I want to know is, is there I way I can force the table to reload and not have it ignore the datasource methods, as is what happens whenever [tableView reloadData] is called.
[tableView reloadData] relies entirely on the data source methods! So, the only way you would see your data source methods beind ignored would be that you have not set the data source (and perhaps delegate) of your table view to be the object you want to be the data source. You can set these through Interface Builder, or programatically, e.g.:
tableView.dataSource = self;
tableView.delegate = self;
whenever the view loads
Where do you call reloadData? Do you call it in -viewDidLoad method? If yes it is probably incorrect, because -viewDidLoad is called only once - when view of the corresponding UIViewController is created (on the first usage). Maybe you should take a look at -viewWillAppear which is called(assuming correct usage of UIViewController) whenever view is going to be displayed.
The other possible reason is that if you do use -viewWillAppear (or -viewDidAppear) it is not triggered at all. This can happen if you use a custom UIViewController hierarchy. In that case you must call it with your own hands (there are exceptions - UINavigationController for example does this for you, but simple [someView addSubview:myController.view] doesn't).
Also please check if delegates are set correctly and tableView is not equal to nil (as you know messages to nil are just ignored).
datasource methods aren't ignored when u call [tableView reloadData];
Did u set the IBOUTLET and the sources right?

Resources