problems with UIViewController switching - ios

I have a little problem with an application.
When I switch the Views there will be a time delay about 3 to 10 seconds, after this app run two sorting algorythmen. (Quick and Bubblesort) in an extra thread. I dont know where the problem exists.
Here the code snippet:
[alert show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) {
time = [Sort sortRuns:(int)slider_durchlaeufe.value WithCapacity:(int)slider_ArraySize.value Reference:alert]; //time is a global reference to my Time object
[self setDataForStatistik]; // a Method to set the statisik for the next view
[alert dismissWithClickedButtonIndex:0 animated:true];
});
I think the failure is in this code snippet.
I hope you can help me.
Robybyte

You need to make UI updates (or any changes that could trigger UI updates) on the main queue. You will see a good deal of this nested "async to background queue then async to main queue" when looking at code samples.
[alert show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) {
time = [Sort sortRuns:(int)slider_durchlaeufe.value WithCapacity:(int)slider_ArraySize.value Reference:alert]; //time is a global reference to my Time object
dispatch_async(dispatch_get_main_queue(), ^{
[self setDataForStatistik]; // a Method to set the statisik for the next view
[alert dismissWithClickedButtonIndex:0 animated:true];
});
});
Hope that helps.

Related

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.

better way of using GCD for threading

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];
});
};

Pushing view controller within block not working

What is the correct way to fire methods within a completion block (if this is even recommended)? Right now, I have an IBAction that calls a method that downloads information with a completion block signifying if the info was retrieved successfully or not. If it was, I want to push a view controller that will display that information, but at the moment, nothing is happening. I'm guessing it has something to do with main thread, gcd, etc...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
Not sure if this is the right way to do it, but it worked.
I added a method that will push the vc on the main thread as so:
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
Completed Code:
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
}];
}
-(void)pushDetail{
__weak YTTMSetupViewController *weakSelf = self;
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
You could simply try wrapping the call with a dispatch_asynch block...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
dispatch_async(dispatch_get_main_queue(), ^{
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
});
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
All UI updates must be performed on the main thread. Personally I prefer to do this through GCD as it produces more readable code than performSelectorOnMainThread. However, there's nothing wrong with performSelectorOnMainThread aside from personal preference in the case of calling a single UI update on the main thread following the execution of some completion block. Do note that, whichever one you choose, you should be consistent with what you use to guarantee that blocks are enqueued in the order you specified.
Working code aside, however, the convention Apple's frameworks seem to use is to perform all completion blocks on the main thread unless a queue is specified as a method parameter, in which case the completion block should be performed on that queue. So in this case I would recommend you edit your download handler class's downloadJson method to automatically perform the completion block on the main queue.

Use dispatch_get_global_queue() in dispatch_get_main_queue

I have started learning "GCD".
I found that when we use the FirstWay, the alertView will be called after all the NSLog functions are done with print.
But when we use the SecondWay, the alertView will be called before the NSLog function.
Why do these two methods run different results?
Can these two methods not be asynchronous methods?
My English is so poor, I hope somebody can understand my description.
Thank you!
/*
*FirstWay
*/
// dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_async(globalQueue, ^{
// for (int i = 0; i < 10000 ; i++) {
// NSLog(#"i = %d", i);
// }
//
// dispatch_async(dispatch_get_main_queue(), ^{
// NSLog(#"i = %d", i);
// UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Title " message:#"Message" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Yes", nil];
// [alertView show];
// });
// });
/*
*SecondWay
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Title " message:#"Message" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Yes", nil];
[alertView show];
dispatch_async(globalQueue, ^{
for (int i = 0; i < 10000 ; i++) {
NSLog(#"i = %d", i);
}
});
});
Here you are submitting a job (task) to a queue by calling the dispatch_async function. Feature of dispatch_async is that it returns immediately, and the submitted block will execute asynchronously in the background.Because UI related tasks run only in the main queue, so you need to return to the main queue if you are in the background queue. And you simply call the dispatch_get_main_queue() method to update the UI.
Here in your case:
In the first case you are submitting the task to the global_queue whereas in the second case you have submitted your task to the main queue.
In the first one , for-loop code is executed from the global_queue and task inside inner dispatch handler is dispatched to the main_queue where it updates the UI (i.e shows the alert view).
In the second one , you are simply submitting to main_queue and you are updating the UI (showing alert) and then you are dispatching to global queue to do for-loop task.
I hope now it helps if not ,feel free to comment.
More Info:
Don't get confused, first just look at the outer dispatch. You are dispatching something to work in the background.You are willing to do that task in the other thread so that your main thread won't have to wait for some long task.(if your main thread takes much time waiting for something then the OS kills your application so GCD is here to take us out of that easily).
Secondly just think the code inside of same outer dispatch as a instructions (line by line codes only). Don't mingle with the inner dispatch handler. Third Go through those line by line.Code upto the start of inner dispatch is the block of code you wish to do in the background.And the reason you nest the inner dispatch is to come out of that background thread(in most cases) in your first case you have done it to show the alert view after your for-loop code completes.

Objective C- Trouble updating UI on main thread

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.

Resources