dispatch_sync block my UI, I cant click any button - ios

I have 2 blocks with dispatch_sync, when the first block ends I show the window for the user and starts run the second block. But I'm not getting click any button on the screen until the second block ends..
Look the code:
[HUD showUIBlockingIndicatorWithText:#"Loading..."];
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
dispatch_sync(dispatch_get_main_queue(), ^{
[UIView beginAnimations:#"fade" context:nil];
[UIView setAnimationDuration:0.5];
self.capa.alpha = 0.0;
[UIView commitAnimations];
[HUD hideUIBlockingIndicator];
});
});
dispatch_barrier_async(queue, ^ {
//code executed in the background
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"entrou na foto");
//[self getFotos];
});
});

If you call any code on the main thread, it will block your UI, so calling
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"entrou na foto");
[self getFotos];
});
will block your UI until [self getFotos] has returned.

A couple of reactions.
The pattern you've used here doesn't quite make sense. Making some inferences from your code sample, I would have thought that the desired pattern would be:
Just start the HUD (or some spinning activity indicator view) in the main queue;
Dispatch all of your separate time consuming processes in the background queue; if they can operate concurrently, you'd use dispatch_async to your queue; and
Do a final dispatch_barrier_async of your completion block (i.e. your indication that all of the other blocks dispatched to your concurrent queue are done), which does the dispatch_async (not sync, generally) back to the main queue to stop the HUD.
There's nothing in this code sample that would suggest anything that would make your UI unresponsive, so I would have to suspect something in the portions of code that you've removed for the sake of clarity/brevity. In general, there are two things that might make your UI unresponsive:
Obviously, if there's anything dispatched back to the main queue that is slow. For example, you have a getFotos method, but we don't know what that does, but if it was slow, that would cause a problem. There's nothing obviously in this category of your snippet, but it's one class of problem to be aware of.
The more subtle problem can be if there's something in that slipped into that background queue accidentally that is UI related. Ironically, that can often cause a UI to freeze for a bit. You'd have to share the details of what you're doing in that block dispatched to the background for us to advise you further on that.
But those are the only two things that leap out at me that could cause your UI to become temporarily unresponsive.
Completely unrelated to your performance issues, but I wouldn't generally advise the old-style animations. As the docs say, "... this method is discouraged in iOS 4.0 and later." So I'd suggest removing the lines that say:
[UIView beginAnimations:#"fade" context:nil];
[UIView setAnimationDuration:0.5];
self.capa.alpha = 0.0;
[UIView commitAnimations];
Instead, I'd suggest you to use block-based animation, such as:
[UIView animateWithDuration:0.5 animations:^{
self.capa.alpha = 0.0;
}];

You may want to use dispatch groups here. Dispach a block into background & into the group with the UI disabled, and then you can spawn a waiting thread calling dispatch_group_wait blocking it until task is done to re-enable the UI. Only the disabled parts are locked. and everything else will work
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, /* queue */, ^{ /* ... */ });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// This thread will be blocked until background tasks are done.
dispatch_async(dispatch_get_main_queue(),
^{ /* ... */ });
});

Related

Issue in execute a method after completion of another method - iOS

I have two methods as loadTopicPostsFromDB and loadTopicPosts. In the loadTopicPostsFromDB method I am updating the value of a global NSString called strLastTimeStamp which should use in the loadTopicPosts. Thus, I want to execute loadTopicPostsFromDB first and after it finished(global string updated) I want to execute loadTopicPosts method.
This is how I did it. But, currently loadTopicPosts method executes before updating the global strLastTimeStamp, so always I get a wrong strLastTimeStamp.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[self performSelectorOnMainThread:#selector(loadTopicPostsFromDB) withObject:nil waitUntilDone:NO];
});
dispatch_group_notify(group, queue, ^{
NSLog(#"LoadDBCompleted");
[self loadTopicPosts];
});
How can I do this, please advice me on what is the wrong in this implementation.
performSelectorOnMainThread: is finished as soon as iOS has put the task into a queue. The selector has most likely not even started running when the call returns. And really, you shouldn't be using performSelectorOnMainThread at all - the function isn't available in Swift, for good reason. The solution is a lot easier (fix the problems yourself):
dispatch_async (dispatch_get_main_queue (), ^{
[self loadTopicsFromDB];
[self loadTopicPosts];
});
You probably want to perform loadTopicsFromDB on a background thread though.
When you are doing something using network connection I advice you to use blocks to handle the endpoint of the call.
It is pretty simple to write in this code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadTopicsFromDB: ^(BOOL success, NSError *error) {
[self loadTopicPosts];
}];
});

Objective-C - Wait for call on another thread to return before continuing

In my iOS application, I have a database call that takes some time to complete. I have a spinner visible on the screen while this operation is taking place. I am hitting an error with the app crashing with "com.myapp failed to resume in time" so it seems like it is running the database call on the main thread, causing issues.
Current Code
-(void)timeToDoWork
{
...
[CATransaction flush];
[[DatabaseWorker staticInstance] doWork];
//Additional UI stuff here
...
if([self->myReceiver respondsToSelector:self->myMessage])
{
[self->myReceiver performSelector:self->myMessage];
}
}
To get the doWork function to take place on a background thread, it looks like I can use Grand Central Dispatch:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[[DatabaseWorker staticInstance] doWork];
});
However, how do I prevent the execution from continuing until it is complete? Should I end the method after the doWork call, and move everything below it to a new function?
Sample
-(void)timeToDoWork
{
...
[CATransaction flush];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[[DatabaseWorker staticInstance] doWork];
dispatch_async(dispatch_get_main_queue(), ^{
[self doneDoingWork];
});
});
}
-(void)doneDoingWork
{
//Additional UI stuff here
...
if([self->myReceiver respondsToSelector:self->myMessage])
{
[self->myReceiver performSelector:self->myMessage];
}
}
Is there a better way to do this?
Prevent execution in main thread from continuing is really bad idea. iOS will terminate your application since main thread should always work with run loop.
I suggest you following way to handle your problem:
Write a "Locker". Let it show some view with animated spinner and no buttons at all.
When you start dispatch async operation just bring it to the front and let it work with run loop.
When your async operation completes close the locker.
You can also use blocks.
e.g..
- (void)doWorkWithCompletionHandler:(void(^)())handler {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// do your db stuff here...
dispatch_async(dispatch_get_main_queue(), ^{
handler();
});
});
}
And then use it like that:
[[DatabaseWorker staticInstance] doWorkWithCompletionHandler:^{
// update your UI here, after the db operation is completed.
}];
P.S.
It might be a good idea to copy the handler block.
The error you are receiving suggests that you are doing something in application:didFinishLaunchingWithOptions: or applicationDidBecomeAction: or somewhere else in the launch cycle that is taking too long and the app is getting terminated by the launch watchdog timer. Above all, it is vital that you return as quickly as possible from these methods. I'm not sure where your code fits into the launch cycle; but this explanation seems plausible.
There are all sorts of ways to address this; but taking the lengthy process off the main queue is the first step as you noted. Without knowing more about what main queue objects (e.g. UI) depend on this database transaction, I'd say that your suggested solution is perfectly fine. That is, dispatch the work to a background queue; and on completion dispatch the remaining UI work to the main queue.
Delegates were suggested elsewhere as a solution. That's also workable although you still have to concern yourself with which queue the delegate methods get called on.
I think that you should use a delegate in your DatabaseWorker and the method doWork always run in background, so when the worker finish the work it tell to its delegate that the work is finished. The delegate method must be called in the main thread.
In the case that you have many objects that need to know when the DatabaseWorker finish instead to use a delegate I would use notifications.
EDIT:
In the DatabaseWorker class you need to implement the method doWork like this:
- (void) doWork{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Do the work.
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate finishWork];
});
});
}
And in the class that implement timeTodoWork:
-(void)timeToDoWork
{
...
[CATransaction flush];
[[DatabaseWorker staticInstance] setDelegate:self];
[[DatabaseWorker staticInstance] doWork];
}
#pragma mark DatabaseWorkerDelegate
- (void) finishWork{
//Additional UI stuff here
...
if([self->myReceiver respondsToSelector:self->myMessage])
{
[self->myReceiver performSelector:self->myMessage];
}
}
Also you can use:
[self performSelectorInBackground:#selector(doWorkInBackground) withObject:nil];
instead of:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//Do the work.
});
And add a method:
- (void) doWorkInBackground{
//Do the work
[self.delegate performSelectorOnMainThread:#selector(finishWork) withObject:nil waitUntilDone:NO];
}

Programmatically linked UIView block-based animations hang at the first time fired on iOS

In my project, the program fire an animation when querying the database, when it receives the response data, it fires another animation automatically to display the data.
-(void)queryDatabase{
//...querying database ...
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//...first animation block...
}
completion:^(BOOL finished){
//...first completion block...
}];
}
-(void)receivedResponse:(NSData *)responseData{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//...second animation block...
}
completion:^(BOOL finished){
//...second completion block...
}];
}
My problem is, when the program is initiated and fire the second animation at the first time received response, the "second animation block" is exactly executed, but the "second completion block" is not executed and the screen does not change until about 20 or more seconds passed. After that, when this cycle is invoked again, the second animation will always work correctly. How to fix?
Firstly, all UI code should execute on the main thread. You will get unexpected results if you don't abide by this. I've experienced nothing happening at all when I want some UI code to run, and apps hanging whenever I've mistakenly run UI code on a background thread. There are plenty of discussions on why UI code has to run on the main thread.
If you know you're receivedResponse method is going to execute on a thread other than the main thread, you can easily bring it back to the main thread using Grand Central Dispatch (GCD). GCD is a lot nicer to use than the performSelectorOnMainThread methods...
-(void)receivedResponse:(NSData *)responseData{
dispatch_async(dispatch_get_main_queue(), ^{
// put your UI code in here
});
}

How to reliably block duplicate animation calls in Objective-C?

A user can initiate an animation with a swipe gesture. I want to block duplicate calls to the animation, to make sure that once the animation has started, it cannot be initiated again until it has completed -- which may happen if the user accidentally swipes multiple times.
I imagine that most people achieve this control using a boolean flag (BOOL isAnimatingFlag) in the manner shown at bottom. I've done things like this before in apps many times -- but I never feel 100% certain as to whether my flag is guaranteed to have the value I intend, since the animation uses blocks and it's unclear to me what thread my animation completion block is being run on.
Is this way (of blocking duplicate animations) reliable for multi-thread execution?
/* 'atomic' doesn't
* guarantee thread safety
* I've set up my flag as follows:
* Does this look correct for the intended usage?
*/
#property (assign, nonatomic) BOOL IsAnimatingFlag;
//…
#synthesize IsAnimatingFlag
//…
-(void)startTheAnimation{
// (1) return if IsAnimatingFlag is true
if(self.IsAnimatingFlag == YES)return;
/* (2) set IsAnimatingFlag to true
* my intention is to prevent duplicate animations
* which may be caused by an unwanted double-tap
*/
self.etiIsAnimating = YES;
// (3) start a new animation
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
// (4) reset the flag to enable further animations
self.IsAnimatingFlag = NO;
}];
}
Disable the gesture if you don't want the user triggering it multiple times
- (void)startTheAnimation:(id)sender
{
[sender setEnabled:NO];
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
[sender setEnabled:YES];
}];
}
Update
Gestures also have an enabled property so you could use the same idea as if it were a button and change it' enabled state
Animation completion block will always run on the main thread.
In the example in the UIView Class Reference you can see that [view removeFromSuperview] is called directly from the block. That's mean a completion block runs on the main thread as it's the only thread safe to call UI-releated methods.
So you are all good if you calling startTheAnimation only from the main thread. If you not you need to dispatch it on the main thread anyway because you call UI-releated methods in it.
If you need to call startTheAnimation from other threads than main thread you can do something like this:
-(void)startTheAnimation{
dispatch_async(dispatch_get_main_queue(), ^{
// Your code here
});
}
Of course, it's better from user experience point of view to, for example, disable a button or modify the UI in other ways to indicate that an animation is in progress. However, it's all the same code. Whatever you need to do you first need to disable it before an animation starts and the re-enable after it's finished.
Where you call this method, you could try using dispatch_once GCD function:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[myThingy startTheAnimation];
});

GCD, order of execution?

Assume we have one UIVewcontroller, call it A, in the viewdidload of that VC we add to it two UIViewcontrollers( B,C ). now to make the UI smooth in the Viewdidload of A we do some GCD work
dispatch_queue_t queue = dispatch_queue_create("CustomQueue", NULL);
dispatch_async(queue, ^{
// Create views, do some setup here, etc etc
// Perform on main thread/queue
dispatch_async(dispatch_get_main_queue(), ^{
// this always has to happen on the main thread
[self.view addSubview:myview1];
[self.view addSubview:myview2];
[self.view addSubview:myview3];
});
});
Now based on this code, am I guaranteed that the views will be added in the same order? view 1 , then 2 , then 3?
I am noticing that arbitrarily some views shows up before others !!
Your problem is almost certainly this part:
dispatch_async(queue, ^{
// Create views, do some setup here, etc etc
You cannot do anything view-related (or really anything UIKit-related) on a background thread. Period.

Resources