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.
Related
Ok. So i am trying to use GCD To Handle all the heavy loading before transition to next view controller. I am opening large archive Files and extracting them which takes some time.
The entire Process is like this:
Click a UICollectionViewCell>Display activity indicator>Let GCD take care of heavy loading>call transition selector using performSelector: onThread:.....
The problem is when i use mainThread, the transition occurs too fast and all the heaving loading don't come to effect until after some time and the transition looks awful and while using currentThread, well it just takes so much time, it seems plain awful of a app.
-(void)someMethod
{
//activity Indicator before transition begins
UIActivityIndicatorView *activity=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activity setFrame:self.view.bounds];
[self.view addSubview:activity];
[self.view bringSubviewToFront:activity];
activity.hidesWhenStopped=YES;
[activity startAnimating];
dispatch_queue_t transitionQueue;
transitionQueue = dispatch_queue_create("com.app.transitionQueue", NULL);
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=.....
dispatch_async(dispatch_get_main_queue(),^{
[activity stopAnimating];
[self transitionToMangaViewer:mReaderPVC];
});
};
}
-(void)transitionToViewer:(ViewerPVC*)viewerPVC
{
[self.navigationController pushViewController:mReaderPVC animated:YES];
}
So Tried The First Suggestion, but the transition still seems buggy since the CollectionViewController still remain On Background For Some Time after the transition
you shouldnt need to use NSThread when you are using gcd, try something like this instead
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=..... //this should block here otherwise will not work
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
[self transitionToAnotherViewer:viewerPVC];
});
});
UI Update should be done on Main Thread, no need to create New Thread for performing UI Transition. Try below code:
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=.....
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
[self performSelector:#selector(transitionToAnotherViewer:) withObject:viewerPVC waitUntilDone:YES];
});
};
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/
});
}
I am new in iOS.
I am trying to implement upload data in uitableview one by one row.
for that i am using background task.
using following code.
-(void)MethodUploadBgTaskAssign
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *responseString = [self MethodlblUploadClicked];
dispatch_async(dispatch_get_main_queue(), ^(void)
{
if([responseString isEqualToString:#"UploadSuccess"])
{
[self ReloadTblData];
[self ContinueUploadData];
}
});
});
}
-(void) ReloadTblData
{
if([dataArr count]>0)
[dataArr removeObjectAtIndex:0];
[uitblData reloadData];
}
-(void) ContinueUploadData
{
if((dataArr count]>0)
[self MethodUploadBgTaskAssign];
}
My problem is uploading data in table after some time table reload with empty data
because all data uploaded at that time.
I want show updated ui after uploading each cell in table.
What will be necessary changes in code?
appreciate for help.
It looks to me as if you are updating the table - but in the wrong thread (hence the table never actually appears to update). You need to use performSelectorOnMainThread to update the UI.
[self performSelectorOnMainThread:#selector(ReloadTblData:) // Update the table on the main thread
withObject:nil
waitUntilDone:NO];
I think this should work - give it a go!
I am having some trouble updating my UI using performSelectorOnMainThread. Here is my situation. In my viewDidLoad I set up an activity indicator and a label. Then I call a selector to retrieve some data from a server. Then I call a selector to update the UI after a delay. Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.reloadSchools = [[UIAlertView alloc] init];
self.reloadSchools.message = #"There was an error loading the schools. Please try again.";
self.reloadSchools.title = #"We're Sorry";
self.schoolPickerLabel = [[UILabel alloc]init];
self.schoolPicker = [[UIPickerView alloc] init];
self.schoolPicker.delegate = self;
self.schoolPicker.dataSource = self;
self.server = [[Server alloc]init];
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:_activityIndicator];
[self.view bringSubviewToFront:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
}
The selector updateUI checks to see if the data was retrieved, and calls a selector on the main thread to update the UI accordingly. Here is the code for these parts:
-(void)updateUI
{
self.schools = [_server returnData];
if(!(self.schools == nil)) {
[self performSelectorOnMainThread:#selector(fillPickerView) withObject:nil waitUntilDone:YES];
}
else {
[self performSelectorOnMainThread:#selector(showError) withObject:nil waitUntilDone:YES];
}
}
-(void)showError {
NSLog(#"show error");
[_activityIndicator stopAnimating];
[self.reloadSchools show];
}
-(void)fillPickerView {
NSLog(#"fill picker view");
schoolList = YES;
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];
self.schoolPickerLabel.text = #"Please select your school:";
self.shoolArray = [[schoolString componentsSeparatedByString:#"#"] mutableCopy];
[self.schoolPicker reloadAllComponents];
[_activityIndicator stopAnimating];
}
When the selector fillPickerView is called the activity indicator keeps spinning, the label text doesn't change, and the picker view doesn't reload its content. Can someone explain to me why the method I am using isn't working to update my ui on the main thread?
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//load your data here.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
});
});
First of all you should not be using detachNewThreadSelector. You should use GCD and submit your background task to an async queue. Threads are costly to create. GCD does a much better job of managing system resources.
Ignoring that, your code doesn't make a lot of sense to me. You submit a method, getSchoolList, to run on a background thread. You don't show the code that you are running in the background.
Then use performSelector:withObject:afterDelay to run the method updateUI on the main thread after a fixed delay of 20 seconds.
updateUI checks for self.schools, which presumably was set up by your background thread, and may or may not be done. If self.schools IS nil, you call fillPickerView using performSelectorOnMainThread. That doesn't make sense because if self.schools is nil, there is no data to fill the picker.
If self.schools is not nil, you display an error, again using performSelectorOnMainThread.
It seems to me that the logic on your check of self.schools is backwards. If it is nil you should display an error and if it is NOT nil you should fill the picker.
Next problem: In both cases you're calling performSelectorOnMainThread:withObject:waitUntilDone: from the main thread. Calling that method from the main thread doesn't make sense.
Third problem: It doesn't make sense to wait an arbitrary amount of time for a background task to run to completion, and then either succeed or fail. You won't have any idea what's going on for the full 20 seconds. If the background task finishes sooner, you'll never know.
Instead, you should have your background task notify the main thread once the task is done. That would be a valid use of performSelectorOnMainThread:withObject:waitUntilDone:, while calling it from the main thread is not. (Again, though, you should refactor this code to use GCD, not using threads directly.
It seems pretty clear that you are in over your head. The code you posted needs to be rewritten completely.
I'm trying to update my textView on screen before it starts downloading data. Right now, it only updates the view after all of the downloads are complete. How can I do it before or in between the downloads?
Edit: I want the self.textView.text = #"Connection is good, start syncing..."; to update the UI before the downloading starts. But right now, it only updates after the download finishes.
Here is what the code looks like.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
[self.textView setNeedsDisplay];
[self performSelectorInBackground:#selector(downloadCustomers:) withObject:error];
}
I'm new to this and have yet to learn how threads work, but from what I read, the downloadCustomers function should be using a background thread leaving the main thread to update the UI.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
//Do whatever you want when your download is finished, maybe self.textView.text = #"syncing finished"
});
});
}
The pattern here is to initialize your download on background thread and then call back to main thread for UI update.
Below is an example using GCD. The advantage of GCD version is that you can consider using whatever you do in -downloadCustomers, to insert in-line where you call it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setNeedsDisplay];
});
});