dispatch_async and frame rate - ios

I'm trying to learn GCD, so I don't have a full grasp on how it works yet. For some reason, I experience a permanent drop in frame rate after I call the following method. If I don't use the dispatch functions and simply write the data on the main loop, the frame rate stays at 60. I don't know why.
-(void)saveDataFile {
_hud = [MBProgressHUD showHUDAddedTo:self.parentView animated:YES];
_hud.labelText = NSLocalizedString(#"Saving data...", nil);
dispatch_queue_t myQueue = dispatch_queue_create("myQueueName", NULL);
dispatch_async(myQueue, ^(void) {
#autoreleasepool {
id data = [self.model getData];
if (data != nil) {
NSString *filePath = #"myPath";
[data writeToFile:filePath atomically:YES];
}
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
[_hud hide:YES];
});
});
}

Solved. I followed the implementation of HUD from this question: MBProgressHUD not showing
Basically, I need to remove the HUD rather than simply hide it. Otherwise the HUD animation continued, invisible to me, hence causing the drop in frame rate.
-(void)saveDataFile {
// create HUD and add to view
MBProgressHUD *hud = [[MBProgressHUD alloc]initWithView:self.parentView];
hud.labelText = NSLocalizedString(#"Saving data...", nil);
hud.delegate = self;
[self.parentView addSubview:hud];
// define block for saving data
void (^saveData)() = ^() {
#autoreleasepool {
id data = [self.model getData];
if (data != nil) {
NSString *filePath = #"myPath";
[data writeToFile:filePath atomically:YES];
}
}
}
// use HUD convenience method to run block on a background thread
[hud showAnimated:YES whileExecutingBlock:saveData];
}
// remove hud when done!
//
- (void)hudWasHidden:(MBProgressHUD *)hud {
[hud removeFromSuperview];
}

Related

iOS writeToURL delay or screen freeze, how to resolve the thread in the case

I have a complex operation, then when the operate complete,
It will save to NSData write to the file to preview.
I encounter a problem, when I click the button ,
the button action will start show MBProgress thing and async to complex operate in background.
When the file write success, It will go to prepareForSegue method pass value to destinationViewController.
I try to add thread, But I found my screen always freeze or can't write file success for stay this screen show the alert(I think it is operate is not complete, so get the BOOL is NO).
How to write the resolve in this case for show the MBProgress wait the operation complete , then navigation to next viewcontroller?
Thank you very much.
My Code below:
- (IBAction)fileBtnAction:(UIButton *)sender{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self doComplexOperateAction];
dispatch_async(dispatch_get_main_queue(), ^{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
});
});
}
- (void) doComplexOperateAction{
....... do something.......
NSError *error;
writeFileSuccess = [resultFilie.fileContent writeToURL:previewFileUrl
options:NSDataWritingFileProtectionComplete
error:&error];
}
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if( [identifier isEqualToString:#"fileSuccessSegue"] ){
if( writeFileSuccess == YES ){
fileHud.hidden = YES;
fileHud = nil;
return YES;
}else{
dispatch_async(dispatch_get_main_queue(), ^{
msgAlertController.message = #"can't write file success";
[self presentViewController:msgAlertController animated:YES completion:nil];
fileHud.hidden = YES;
fileHud = nil;
});
return NO;
}
}
return NO;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if( [[segue identifier] isEqualToString:#"fileSuccessSegue"] ){
.........
NSURL *fileURL = [NSURL fileURLWithPath:fileTmpPathString];
fileSuccessViewController *pfVC = [segue destinationViewController];
pfVC.filePathURL = fileURL;
}
}
I think It will be something like this:
#property (noatomic) BOOL uploading;
- (IBAction)fileBtnAction:(UIButton *)sender{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self doComplexOperateAction];
dispatch_sync(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"fileSuccessSegue" sender:nil];
});
});
}
- (void) doComplexOperateAction{
self.uploading = YES;
....... do something.......
NSError *error;
writeFileSuccess = [resultFilie.fileContent writeToURL:previewFileUrl
options:NSDataWritingFileProtectionComplete
error:&error];
self.uploading = NO;
}
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if( [identifier isEqualToString:#"fileSuccessSegue"] ){
if( writeFileSuccess == YES ){
fileHud.hidden = YES;
fileHud = nil;
return YES;
}else{
msgAlertController.message = #"can't write file success";
[self presentViewController:msgAlertController animated:YES completion:nil];
fileHud.hidden = YES;
fileHud = nil;
});
return NO;
}
}
return !self.uploading;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if( [[segue identifier] isEqualToString:#"fileSuccessSegue"] ){
.........
NSURL *fileURL = [NSURL fileURLWithPath:fileTmpPathString];
fileSuccessViewController *pfVC = [segue destinationViewController];
pfVC.filePathURL = fileURL;
}
}
I would advise you to rewrite this code. I do not like how you handle errors. Because writeFileSuccess have unsuspected value while you uploading content.
I couldn't understand if the file was written or no. Anyway, keep in mind that if you try to dispatch to main thread you should always first check if you are already on the main thread, because it can hang the application.
Example:
if([NSThread isMainThread]){
//perform gui operation
} else {
dispatch_async(dispatch_get_main_queue(), ^{
//perfrom gui operation
});
}
Edit: You should consider using dispatch_sync instead of dispatch_async for the main thread, since you have nested dispatches in - (IBAction)fileBtnAction:(UIButton *)sender:
[self doComplexOperateAction];
dispatch_**sync**(dispatch_get_main_queue(), ^{
fileHud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
fileHud.label.text = #"file operating....";
fileHud.userInteractionEnabled = NO;
});
});

MBProcessHUD show/hide when using semaphore

Hi I m using dispatch_group_t for async web call and during that wait period I want to show activity indicator.
Here is my code which is not working for show/hide activity indicator.
- (BaseResponse*)getResponse:(BaseRequest*)request{
request.header = [[Header alloc]init];
request.header.osType = 2;
NSString *jsonBody = [request toJSONString];
NSURL *requestURL = #"myurl";
__block BaseResponse *baseResponse;
[self showHUDProcessView]; //here activity indicator should start animating but not working as per expected.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[COMHandler sharedCOMHandler] makeServiceCall:jsonBody requestURL_:requestURL completion:^(ComResponse *comResponse){
[self hideHUDProcessView]; //hide activity when receive response
if (comResponse != nil) {
baseResponse = comResponse;
}else{
//show alert
}
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return baseResponse;
}
- (void) showHUDProcessView {
[MBProgressHUD showHUDAddedTo:viewController.view animated:YES];
}
- (void) hideHUDProcessView {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:viewController.view animated:YES];
});
});
}
I also tried with starting group in block but also not work..
Tried
dispatch_semaphore_t activity = dispatch_semaphore_create(0);
//show indicator
^myblock {
//hide indicator
dispatch_semaphore_signal(activity);
}
dispatch_semaphore_wait(activity, DISPATCH_TIME_FOREVER);
also but no luck.
Can anybody suggest me when i m doing wrong??
Or want else I can do to achieve this indicator show/hide issue.

MBProgressHUD showAnimated: whileExecutingBlock: not working

I'm using a method for scanning Bluetooth device, which I import from another framework. The scanning method would take a while, and it'll block the GUI, which is what we never want to happen.
I'm also having MBProgressHud, trying to show a hud while scanning, but it's not working (hud not showing up). Any help?
Here's the code I'm currently using:
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
}];
Edit 1: Okay, so if I use this code, it'll still block my UI for a while, then all suddenly continue to run.
hud = [[MBProgressHUD alloc] initWithView:self.view];
hud.labelText = #"Now scanning";
hud.dimBackground = YES;
hud.opacity = 0.5;
[hud show:YES];
[hud hide:YES afterDelay:5.0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
self.btDevices = [Util scanBT];
});
Edit 2: Ok, I will post all my block of code:
hud = [[MBProgressHUD alloc] initWithView:[self getTopView:self.view]];
hud.labelText = #"Now scanning";
hud.dimBackground = YES;
hud.opacity = 0.5;
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
}];
dispatch_queue_t myqueue = dispatch_queue_create("queue", NULL);
dispatch_async(myqueue, ^{
//Whatever is happening in the BT scanning method will now happen in the background
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:[self getTopView:self.view] animated:YES];
});
});
/** Recursion to get the top most view in view layer. */
- (UIView *)getTopView:(UIView *)view
{
if ([view.superview class]) {
return [self getTopView:view.superview];
}
return view;
}
I'm request scanning bt in a popover, but I want to show HUD in a main View, so I write a block to retrieve the main view. Maybe that's where the problem occur?
Try this:
In your viewDidload or the method where you want to place it
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self.view];
[hud setDimBackground:YES];
[hud setOpacity:0.5f];
[hud show:YES];
[hud hide:YES afterDelay:5.0];
[self performSelector:#selector(startScanning) withObject:nil afterDelay:5.0];
And your method
- (void) startScanning {
self.btDevices = [Util scanBT];
}
OR I think you should try it running
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
// Insert task code here
self.btDevices = [Util scanBT];
});
Try it with completion block
[hud showAnimated:YES whileExecutingBlock:^{
self.btDevices = [Util scanBT];
} completionBlock:^{
//code for after completion
}];
OR you can also try this
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_queue_t myqueue = dispatch_queue_create("queue", NULL);
dispatch_async(myqueue, ^{
//Whatever is happening in the BT scanning method will now happen in the background
self.btDevices = [Util scanBT];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});

How to reload tableView after async fetching JSON ? Objective C

This is my code for async fetching JSON
-(void)viewDidAppear:(BOOL)animated
{
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Load the json on another thread
[Constants shared].jsonData = [[NSData alloc] initWithContentsOfURL:
[NSURL URLWithString:#"http://api.fessor.da.kristoffer.office/homework/index/?rp[token]=app&rp[workspace]=parent&child_id=22066&type=parent&start_date=2014-05-01&end_date=2014-05-01"]];
//When json is loaded stop the indicator
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
[self declareVariables];
[_tableView reloadData];
});
}
And it's not working for some reason.
It shows the spinner, I can see that the data is fetched, it stops and hides the spinner, but the tableView is not reloaded, it's blank.
You want to move all the UI stuff to the main queue. So your code should read as:
-(void)viewDidAppear:(BOOL)animated
{
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Load the json on another thread
[Constants shared].jsonData = [[NSData alloc] initWithContentsOfURL:
[NSURL URLWithString:#"http://api.fessor.da.kristoffer.office/homework/index/?rp[token]=app&rp[workspace]=parent&child_id=22066&type=parent&start_date=2014-05-01&end_date=2014-05-01"]];
dispatch_async(dispatch_get_main_queue(), ^{
//When json is loaded stop the indicator
[_indicator stopAnimating];
[self declareVariables];
[_tableView reloadData];
});
});
}
This way your JSON is loaded on the background queue and the UI is updated on the main queue.
You should try to reload your table view on the main thread. Use GCD to dispatch on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
If there's no result, try to log in the cellForRowAtIndexPath method to check whether the reload is done or not. If so, you error is elsewhere.

MBProgressHUD is displayed only when the operation is finished

i'm using MBProgressHUD to show a popup with a loading animation but i have a problem. The progress indicator call is executed when i press a button. This is the action
- (IBAction)calendarioButtonPressed:(UIButton *)sender {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeIndeterminate;
hud.labelText = #"Uploading";
[hud show:YES];
[self getCalendarioJson];
}
in the getCalendarioJson i have this
- (void) getCalendarioJson {
//LETTURA CALENDARIO - INIZIO
NSString *calLink = myLink;
NSData* responseData = [NSData dataWithContentsOfURL:[NSURL URLWithString:calLink]];
NSArray* json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions error:nil];
NSDictionary *firstObject = [json objectAtIndex:0];
NSDictionary *cal = [firstObject objectForKey:#"output"];
variabiliGlobali.calendario = [[NSMutableArray alloc] init];
for (NSDictionary *dict in cal) {
[variabiliGlobali.calendario addObject: dict];
}
//LETTURA CALENDARIO - FINE
}
Why does the loading popup appear only at the end of getCalendarioJson execution?
The button have a segue. When i go back from the target view i can see the popup.
What is the metter? If i delete the segue i can see the popup at the end of getCalendarioJson execution (because there is not a segue).
Thanks in advance.
You have a method [self getCalendarioJson]; but this method run in the main thread, the main thread is for UI updates, when you download data or make a operation with several seconds of duration, the main thread wait for make the UI update.
For solve this situation, put the method in a background thread, can you use [self performSelectorInBackground:#selector(getCalendarioJson) withObject:nil];
From https://github.com/jdg/MBProgressHUD
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.labelText = #"Loading";
[self doSomethingInBackgroundWithProgressCallback:^(float progress) {
hud.progress = progress; //Here you set the progress
} completionCallback:^{
[hud hide:YES];
}];
You should set the progress value.

Resources