UITableView reloadData and #catch block - ios

[UITableView reloadData] not called in #catch block
this is my code:
#try {
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
#catch (NSException *exeption) {
[self.tableView reloadData];
}
Sometimes problems appears when inserting new rows in tableview (it doesn't matter which exactly) and I would like to handle it. While I testing exception raises #catch block handles it and crash not happens, but reloadData also not calling. I also tried to call reloadData on main thread manually with perfomSelectorOnMainThread: and GCD:
#catch (NSException *exeption) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
but it also didn't give any effect. Can somebody suggest something? Thanks!

In Objective-C, it's not a good idea to catch the exception and keep going. It's better to figure out what's wrong and prevent the exception from happening.
But for whatever reason, you can try -performSelector:withObject:afterDelay:.
[self.tableView performSelector:#selector(reloadData) withObject:nil afterDelay:0.0];
Update
-performSelector:withObject:afterDelay: always runs in a later run loop even when the delay is 0.
[self.tableView performSelector:#selector(reloadData) withObject:nil afterDelay:0.0];
The effect is to call the -reloadData method in the next run loop. This is useful when there are UI changes are pending in the current run loop.

Related

iOS 10: NSFetchedResultsController + UICollectionView, SIGABRT on performBatchUpdates

I'm using this gist for FRC and UICollectionView. This was working fine till iOS 9.
Now, in iOS 10 sometimes my app crashes with SIGABRT signal crash at performBatchUpdates of collectionview. Even if the CollectionView escapes from crash it goes in coma with 1 or 2 cells.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// Checks if we should reload the collection view to fix a bug # http://openradar.appspot.com/12954582
if (self.shouldReloadCollectionView) {
[self.collectionView reloadData];
} else {
[self.collectionView performBatchUpdates:^{ // CRASH : Thread 1: signal SIGABRT
[self.blockOperation start];
} completion:nil];
}
}
Is this happening because of new upgraded functionality of UICollectionView? What's the fix?
After doing some research, found a fix for this. My app fetches data from a web server and inserts it using main thread.
I assumed that this signal is getting raised because of some kind of invalid data manipulation. As I doubted controllerDidChangeContent( main thread ) delegate is getting called as soon as the thread starts saving the data. [self.managedObjectContext save:&savingError];
This early invocation causes performBatchUpdates to indulge in data manipulation in the middle of the saving process which results in crash.
Putting the controllerDidChangeContent code inside dispatch_async fixed the crash and the coma state of CollectionView. I hope this helps someone.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
dispatch_async(dispatch_get_main_queue(), ^{
// Checks if we should reload the collection view to fix a bug # http://openradar.appspot.com/12954582
if (self.shouldReloadCollectionView) {
[self.collectionView reloadData];
} else {
[self.collectionView performBatchUpdates:^{ // No crash :)
[self.blockOperation start];
} completion:nil];
}
});
}

Why does a strong reference to parent UIViewController in performBatchUpdates leak an activity?

I just finished debugging a very nasty UIViewController leak, such that the UIViewController was not dealloc'd even after calling dismissViewControllerAnimated.
I tracked down the issue to the following block of code:
self.dataSource.doNotAllowUpdates = YES;
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadItemsAtIndexPaths:#[indexPath]];
} completion:^(BOOL finished) {
self.dataSource.doNotAllowUpdates = NO;
}];
Basically, if I make a call to performBatchUpdates and then immediately call dismissViewControllerAnimated, the UIViewController gets leaked and the dealloc method of that UIViewController never gets called. The UIViewController hangs around forever.
Can someone explain this behavior? I assume performBatchUpdates runs over some time interval, say, 500 ms, so I would assume that after said interval, it would call these methods and then trigger the dealloc.
The fix appears to be this:
self.dataSource.doNotAllowUpdates = YES;
__weak __typeof(self)weakSelf = self;
[self.collectionView performBatchUpdates:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
} completion:^(BOOL finished) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
strongSelf.dataSource.doNotAllowUpdates = NO;
}
}];
Note that the BOOL member variable, doNotAllowUpdates, is a variable I added that prevents any kind of dataSource / collectionView updates while a call to performBatchUpdates is running.
I searched around for discussion online about whether or not we should use the weakSelf/strongSelf pattern in performBatchUpdates, but did not find anything specifically on this question.
I am happy that I was able to get to the bottom of this bug, but I would love a smarter iOS developer to explain to me this behavior I am seeing.
This seems like a bug with UICollectionView. API users should not expect single-run block parameters to be retained beyond the execution of the task, so preventing reference cycles should not be an issue.
UICollectionView should be clearing up any references to blocks once it has finished the batch update process, or if the batch update process is interrupted (for example, by the collection view being removed from the screen).
You've seen for yourself that the completion block is called even if the collection view is taken off-screen during the update process, so the collection view should then be nilling out any reference it has to that completion block - it will never be called again, regardless of the current state of the collection view.
As you figured out, when weak is not used a retain cycle is created.
The retain cycle is caused by self having a strong reference to collectionView and collectionView now has a strong reference to self.
One must always assume that self could have been deallocated before an asynchronous block is executed. To handle this safely two things must be done:
Always use a weak reference to self (or the ivar itself)
Always confirm weakSelf exists before passing it as a nunnull
param
UPDATE:
Putting a little bit of logging around performBatchUpdates confirms a lot:
- (void)logPerformBatchUpdates {
[self.collectionView performBatchUpdates:^{
NSLog(#"starting reload");
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
NSLog(#"finishing reload");
} completion:^(BOOL finished) {
NSLog(#"completed");
}];
NSLog(#"exiting");
}
prints:
starting reload
finishing reload
exiting
completed
This shows that the completion block is fired AFTER leaving the current scope, which means it is dispatched asynchronously back to the main thread.
You mention that you immediately dismiss the view controller after doing the batch update. I think this is the root of your issue:
After some testing, the only way I was able recreate the memory leak was by dispatching the work before dismissing. It's a long shot, but does your code look like this by chance?:
- (void)breakIt {
// dispatch causes the view controller to get dismissed before the enclosed block is executed
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
} completion:^(BOOL finished) {
NSLog(#"completed: %#", self);
}];
});
[self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}
The code above leads to dealloc not being called on the view controller.
If you take your existing code and simply dispatch (or performSelector:after:) the dismissViewController call you will likely fix the issue as well.

Upload new items to table view

I have table view that load new data (depend on page) from SQL data base. Problem is, when i load it in main thread, it block UI for a while. When i try to do "hard work" in background, and reload data in main thread, odd things start to happen, for example, table view section header move in wrong place, and i load enormous amount of data.
First case, all work but block UI for while:
[self.tableView addInfiniteScrollingWithActionHandler:^{
#strongify(self)
if (!self.viewModel.isUpdating){
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
}];
In second case, i tried to do background work, following not work as expected:
if (!self.viewModel.isUpdating){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Add some method process in global queue - normal for data processing
self.viewModel.isUpdating = YES;
[self.tableView.infiniteScrollingView startAnimating];
[self.viewModel nextPage];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
//Control UIView, IBOutlet all here
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
});
//Add some method process in global queue - normal for data processing
});
}
}];
How should i modify my code to not load main thread, and without "weird" things?
have you tried something like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
.....
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelectorOnMainThread:#selector(updateView) withObject:nil waitUntilDone:YES];
});
});
......
-(void)updateView{
[self.tableView reloadData];
self.viewModel.isUpdating = NO;
}
user PerformSelectorOnMainThread it may help you.

Does reloadData of Tableview execute immediately in this case

I read that [_tableView reloadData] sends a message to the main queue to update and display the data in the Table View. Based on this I would like to discuss the following case. Suppose another method sends a message to the main queue before [_tableView reloadData] in that case will the second message get processed before [_tableView reloadData] ?
Now this is my case
Suppose I have two threads TA and TB and I have two methods MethodAand MethodB which look like this
This is MethodA
- (void) MethodA
{
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData]
});
}
This is MethodB
- (void) MethodB
{
dispatch_async(dispatch_get_main_queue(), ^{
//Runs under the assumption the "SomeObject" has already been displayed in Tableview
//Make changes to TableView/
});
}
Suppose that MethodB is called by ThreadB and occurs during TimeFRameA.
in that case will MethodB be called before [_tableView reloadData] ?
Is there any way for me to make sure that the MethodB only runs when the tableView is displaying the updated data ?
The main dispatch queue (which is associated with the main thread)
is a serial queue, not concurrent. Therefore it cannot happen
that in
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData]
});
}
any other code executes on the main queue between adding the object
and reloading the table view.
Any other block dispatched to the main queue executes either before
or after this block.
I would recommend you to use "dispatch_group" to synchronize.
Create a dispatch group object by calling:
dispatch_group_t group = dispatch_group_create();
In MethodA use:
-(void) MethodA {
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
[Myarray addObject:#"SomeObject"];
///----------<TimeFrameA>----------------
[_tableView reloadData];
dispatch_group_leave();
});
}
In MethodB wait for the group to complete and then perform further operations:
-(void) MethodB {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//Runs under the assumption the "SomeObject" has already been displayed in Tableview
//Make changes to TableView/
});
}

[self.tableview reloadData]; causes flickering

The problem is the UI appears and then gets updated : giving a flickering affect.
I want the UI to be updated only once when user enters app, thus i've put reload in ViewDidLoad.. Here is the code .. Any help how can remove this flickering ... Some code example would help.
- (void)viewDidLoad {
[super viewDidLoad];
self.myTableView.dataSource = self;
self.myTableView.delegate = self;
PFQuery * getCollectionInfo = [PFQuery queryWithClassName:#"Collection"]; // make query
[getCollectionInfo orderByDescending:#"updatedAt"];
[getCollectionInfo setCachePolicy:kPFCachePolicyCacheThenNetwork];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[getCollectionInfo findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
CollectionQueryResult = (NSMutableArray *)objects;
[self.tableView reloadData];
// whenevr get result
}
else{
//no errors
}
}];
});
Why don't you simply call reloadSections method instead of [self.tableView reloadData];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
Refer cellForRowAtIndexPath delegate method. You might be doing some operation that might cause a flick. In my case, I was setting an image of an image view with animation. So whenever I reload the table, it was flickering due to that animation.
I removed that animation and worked for me..
Hope the below lines can help you in delaying and flickering issues
dispatch_async(dispatch_get_main_queue()
, ^{
[self.tableView reloadData];
});
Your download is asynchronous, so it won't complete before the view is shown. So, whatever you have in CollectionQueryResult will get displayed.
You could either:
Clear CollectionQueryResult so the table isn't populated till you get an update
Compare CollectionQueryResult and objects, then update only the visible cells where the data has changed (before setting CollectionQueryResult)
Note that for option 2 you will also need to compare the count of CollectionQueryResult and objects and then insert / delete rows from the table view as appropriate. The whole point of option 2 is to not call reloadData, so you need to do a lot more work...
You can also avoid calling reloadRowsAtIndexPaths if you get the visible cells and update them directly (which avoids any animation from happening). See cellForRowAtIndexPath:.

Resources