When I first call following code from dispatch queue, completion block not called.
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t initialize_queue;
initialize_queue = dispatch_queue_create("init", NULL);
dispatch_async(initialize_queue, ^{
_onInit = YES;
_isRunning = NO;
[self startProgress];
_onInit = NO;
});
}
- (void)startProgress
{
if (!_isRunning) {
_isRunning = YES;
NSLog(#"Starting");
[UIView animateWithDuration:0.5 animations:^{
self.progressStatusButton.hidden = NO;
self.activityLeftConstraint.constant = _activityLeftSpace;
self.activityWidthConstraint.constant = _activityWidth;
self.buttonWidthConstraint.constant = _progressStatusButtonWidth;
self.buttonLeftConstraint.constant = _progressStatusButtonLeftSpace;
self.activityView.alpha = 1.0;
}completion:^(BOOL finished){
NSLog(#"Start Animating");
[self.activityView startAnimating];
}];
}
}
When I delete dispatch_aync method and execute startProgress method in viewDidLoad,
completion block called. How should I correct my code?
I tried to change dispatch_async to use dispatch_async(dispatch_get_main_queue(),..),
then completion block called, but I would like to execute asynchronously startProgress method.
Please Let me know.
Everything connected with GUI should be called from main thread/ pushed to main queue. In other case expected behavior is not guaranteed.
So you should use
dispatch_get_main_queue()
Also animation ALWAYS executed asynchronously so I do not think you really need separate queue for it.
Related
In my project there's a ViewController which contains a few subviews(e.g. buttons).
It shows/hides those buttons, always with animation.
It has an interface like this:
#interface MyViewController: UIViewController
- (void)setMyButtonsVisible:(BOOL)visible;
#end
And an implementation looks like this:
- (void)setMyButtonsVisible:(BOOL)visible
{
if( visible )
{
// "show"
_btn1.hidden = NO;
_btn2.hidden = NO;
[UIView animateWithDuration:0.2 animations:^{
_btn1.alpha = 1.0;
_btn2.alpha = 1.0;
}];
}
else
{
// "hide"
[UIView animateWithDuration:0.2 animations:^{
_btn1.alpha = 0.0;
_btn2.alpha = 0.0;
} completion:^(BOOL finished) {
_btn1.hidden = YES;
_btn2.hidden = YES;
}];
}
}
When [_myVC setMyButtonsVisible:NO] is called, and then after some time ( > 0.2s) [_myVC setMyButtonsVisible:YES] is called, everything is OK.
However, it setMyButtonsVisible:YES is called immediately after ( < 0.2s) setMyButtonsVisible:NO, the animations overlap and setMyButtonsVisible:NO callback is called the last.
I've tried to change "hide" duration to 0.1, it doesn't help - after "hide(0.1)"+"show(0.2)" calls, "hide" callback is called after the "show" callback and my buttons are not visible.
I've added a quick-fix by caching the visible param and checking in "hide" completion handler if the state should be !visible.
My questions are:
Why the first animation completion handler is called the last if animations overlap?
What are better approahes to discard a previous "overlapping" animation?
Check the finished flag on completion:
if (finished) {
_btn1.hidden = YES;
_btn2.hidden = YES;
}
I would like to run a concurrent NSOperation. For this reason, I have extended the NSOperation class and overridden the start and finish methods. It looks something like this:
#import "AVFrameConversionOP.h"
#implementation AVFrameConversionOP //extends NSOperation
- (void)start
{
[self willChangeValueForKey:#"isFinished"];
_isFinished = NO;
[self didChangeValueForKey:#"isFinished"];
// Begin
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
if (_isCancelled == YES) {
NSLog(#"** OPERATION CANCELED **");
[self willChangeValueForKey:#"isFinished"];
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self performTask];
[self finish];
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
NSLog(#"operationfinished, _isFinished is %d", _isFinished);
if (_isCancelled == YES)
{
NSLog(#"** OPERATION CANCELED **");
}
}
- (void)performTask
{
//The task is performed here
sleep(0.1); //This is just for demonstration purposes
}
#end
I have defined the completion block for this NSOperation in the main view controller in the viewDidLoad method:
- (void)viewDidLoad {
NSLog(#"viewDidLoad!");
[super viewDidLoad];
static int counter = 0;
_frameConvOP = [[AVFrameConversionOP alloc] init]; //Initializing the modified NSOperation class
__weak typeof(self) weakSelf = self;
_frameConvOP.completionBlock = ^ {
counter++;
//NSLogs don't seem to work here
/*if(counter>1){
exit(-1); //Never happens because counter only ever becomes =1
}*/
};
[[VideoPreviewer instance] setNSOperation:_frameConvOP]; //This is where I send the NSOperation object to the class where I'm using it
}
I'm starting the NSOperation by doing [frameConvOP start] inside of a loop. It is called continuously every time a frame is decoded, so that is to say it is called very often. I can see that the finish and start methods of the NSOperation are being called frequently through the logs. And the functions inside these methods are also being correctly called. However, the NSLogs inside the completion blocked aren't logged. I put an exit(-1) inside the completion block and the app crashed, but the NSLogs never show up. So I put a static counter in there and it only increments to 1. So the completion block is only being called once.
Can anyone explain why the completion block is being called only once? Is it because of the way I'm (incorrectly) handling the KVO for _isFinished and _isExecuting? The completion block should be called when _isFinished is set to YES and as far as I can see, I am doing that in the finish block. And the finish block is being called quite frequently as it should be.
Any help, guidance or direction is appreciated. Please let me know if I have made any errors or if I have been less-than-clear in the post.
Thanks,
From the NSOperation class reference:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
You are not allowed to invoke -start multiple times on a single operation object.
Another thing, in your -finish method, you need to properly nest the -willChange... and -didChange... calls. That is, you need to call -didChange... in the reverse order of the -willChange... calls:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
I want to show loading animation while I am doing some initialisation. So I want to spin my (UIImageView*)_LogoImage until my initialisation will be completed. Then, after initialisation will be finished, I want to scale my _LogoImage. So, all this things begins from viewDidAppear, where I am calling beginLoading: method.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self beginLoading];
}
Here, I am starting my spin animation. Assume, I am doing some initialisation in the next two code lines, I changed them to make a thread sleep to make a similar behaviour of my initialisation code. Then I am calling stopSpin: method to make the next half circle and to do my last scaling animation.
-(void)beginLoading{
[self startSpin];
[self sleepAction:3.0f];
[self sleepAction:1.0f];
[self stopSpin];
}
-(void)sleepAction:(float)sleepTime{
NSLog(#"SleepTime:[%f]",sleepTime);
[NSThread sleepForTimeInterval:sleepTime];
}
Here's my spinning code, which I am calling recursively until my BOOL refreshAnimating is Equal to YES. if not - running the last scaling animation
-(void)startSpin{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform = CGAffineTransformMakeRotation(0);
}
completion:^(BOOL finished){
if (refreshAnimating) {
[self startSpin];
}else{
[self refreshFinished];
}
}];
}];
}
-(void)stopSpin{
refreshAnimating = NO;
}
-(void)refreshFinished{
[UIView animateWithDuration:0.4 animations:^{
[_LogoImage setTransform:CGAffineTransformMakeScale(2, 2)];
}];
}
The problem is that my animation is not running, until the last initialisation code line completes. And after completion - I can see only the last animation. I was trying to put some code performing in the background and main thread, but I didn't run the way I want.
The rotation animation should start when the view appears, and continue until my initialisation code complete - then I want to see my last scale animation, which will indicate that my initialisation is completed.
Please, help.
All animations run on main thread and if you use sleep, it blocks your animations(main thread). Instead you can use a library like: MBProgressHUD please feel free to ask further questions.
Edit
-(void)startSpin{
_LogoImage.transform = CGAffineTransformMakeRotation(M_PI);
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
_LogoImage.transform =CGAffineTransformMakeRotation(0);
}
completion:nil];
}
Can you try this. I think that is what are trying to do.
As #Ersin Sezgin said, all animations run on main thread.
So now, I am doing initialisation in background with completion block handler, which is forcing stopSpin: after initialisation completes.
Here's some code block. For better readability there's typedef of block.
typedef void(^OperationSuccessCompletionHandler)(BOOL success);
in .m file
-(void)sleepAction:(OperationSuccessCompletionHandler)success{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// We are doing initialisation here
BOOL isOperationSuccess = YES // Assume initialisation was successful
success(isOperationSuccess);
});
}
So now we can do this in another way
-(void)beginLoading{
[self startSpin];
[self sleepAction:^(BOOL success){
[self sleepAction:^(BOOL success){
[self stopSpin];
}];
}];
}
One of my methods (mySecondMethod) receives a block and need to add an additional treatment to this block before passing it as an argument to another method.
Here is the code sample:
- (void)myFirstMethod {
// some code
__weak MyController *weakSelf = self;
[self mySecondMethod:^(BOOL finished) {
[weakSelf doSomething:weakSelf.model.example];
}];
}
- (void)mySecondMethod:(void(^)(BOOL finished))completion {
void (^modifiedCompletion)(BOOL) = ^void(BOOL finished){
completion(finished);
_messageView.hidden = YES; //my new line
};
[UIView animateWithDuration:duration animations:^{
//my code
} completion:modifiedCompletion];
}
When run, I got a bad access error on completion(finished) line. completion is NULL. I tried to copy the block like this:
void (^copiedCompletion)(BOOL) = [completion copy];
void (^modifiedCompletion)(BOOL) = ^void(BOOL finished){
copiedCompletion(finished);
_messageView.hidden = YES; // my new line
};
but still got the error.
When I empty out completion block, the crash still happen so the crash is not due to what is inside.
Any idea how to solve this? Thanks!
I think you are getting the bad access because of this
// some code
__weak MyController *weakSelf = self;
[self mySecondMethod:^(BOOL finished) {
[weakSelf doSomtehing:weakSelf.model.example];
}];
Try changing it to.
id example = self.model.example;
[self mySecondMethod:^(BOOL finished) {
[self doSomething:example];
}];
Edit
The block needs to be copied before being called.
Side Note
Do a check before calling blocks to avoid unexpected crashes.
if (completion) {
completion(finished);
}
I can call some selector by this code
[self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]
and the Thread will wait until this selector is working (if I set waitUntilDone:YES). My question is - Can i wait until some code is done?
for example. I have some subview, which loads with animation. Animation duration is 2 sec. And if i make some actions on background view, while animating, i have an error. I'd like to wait until animation is over. And I can't to make a selector, and use
[self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]
Any suggestions? )
You can try this way
-(void)waitUntilDone:(void(^)(void))waitBlock {
//use your statement or call method here
if(waitBlock){
waitBlock();
}
}
And you need to use it as
[self.tableView waitUntilDone:^{
//call the required method here
}];
UIView animation gives this to you in the form of the block style animation
[UIView animateWithDuration:0.25f
animations:^{
// animations
} completion:^(BOOL finished) {
// run code when the animation is finished
}];
Wait until animation gets finished & then do rest of the things :
For eg:
[UIView animateWithDuration:duration
animations:^
{
//DO ANIMATION ADJUSTMENTS HERE
}
completion:^(BOOL finished)
{
//ANIMATION DID FINISH -- DO YOUR STUFF
}];