Crear un método para realizar animaciones y esperar a que se complete utilizando un semáforo en el objective c

Estoy tratando de crear un método que haga uso del método "+ animateWithDuration: animations: completion" de UIView para realizar animaciones y esperar a que se complete. Estoy muy consciente de que podría colocar el código que normalmente vendría después de él en un bloque de finalización, pero me gustaría evitar esto porque hay una cantidad considerable de código después de esto, incluyendo más animaciones, lo que me dejaría con bloques nesteds .

Intenté implementar este método de la siguiente manera usando un semáforo, pero no creo que esta sea la mejor manera de hacerlo, especialmente porque en realidad no funciona. ¿Alguien puede decirme qué está mal con mi código y cuál es la mejor manera de lograr el mismo objective?

+(void)animateAndWaitForCompletionWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [UIView animateWithDuration:duration animations:animations completion:^(BOOL finished) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } 

No estoy seguro de cuál es el problema con mi código, pero cuando llamo al método como se muestra a continuación, el bloque de finalización nunca funciona y termino en el limbo.

 [Foo animateAndWaitForCompletionWithDuration:0.5 animations:^{ //do stuff }]; 

——————————————-EDITAR—— ——————————————-

Si alguien se enfrenta a un problema similar, puede que le interese ver el código que usé. Utiliza la recursion para utilizar cada bloque de finalización, sin tener que anidar cada llamada de function.

 +(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations { [Foo animateBlocks:animations withDurations:durations atIndex:0]; } +(void)animateBlocks:(NSArray*)animations withDurations:(NSArray*)durations atIndex:(NSUInteger)i { if (i < [animations count] && i < [durations count]) { [UIView animateWithDuration:[(NSNumber*)durations[i] floatValue] animations:(void(^)(void))animations[i] completion:^(BOOL finished){ [Foo animateBlocks:animations withDurations:durations atIndex:i+1]; }]; } } 

que se puede usar como tal

 [Foo animateBlocks:@[^{/*first animation*/}, ^{/*second animation*/}] withDurations:@[[NSNumber numberWithFloat:2.0], [NSNumber numberWithFloat:2.0]]]; 

En iOS 7 y posteriores, generalmente se emplea animation de fotogtwigs key para lograr este efecto.

Por ejemplo, una secuencia de animation de dos segundos que se compone de cuatro animaciones separadas que ocupan el 25% de la animation completa, se vería así:

 [UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{ [UIView addKeyframeWithRelativeStartTime:0.00 relativeDuration:0.25 animations:^{ viewToAnimate.frame = ...; }]; [UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.25 animations:^{ viewToAnimate.frame = ...; }]; [UIView addKeyframeWithRelativeStartTime:0.50 relativeDuration:0.25 animations:^{ viewToAnimate.frame = ...; }]; [UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{ viewToAnimate.frame = ...; }]; } completion:nil]; 

En versiones anteriores de iOS, podrías hacer una serie de animaciones de varias maneras, pero te animaría a evitar utilizar un semáforo en el hilo principal.

Un enfoque sería envolver la animation en una subclass NSOperation concurrente, que no se completa hasta que la animation lo haga. A continuación, puede agregar sus animaciones a su propia queue de serie personalizada:

 NSOperationQueue *animationQueue = [[NSOperationQueue alloc] init]; animationQueue.maxConcurrentOperationCount = 1; [animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{ viewToAnimate.center = point1; }]]; [animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{ viewToAnimate.center = point2; }]]; [animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{ viewToAnimate.center = point3; }]]; [animationQueue addOperation:[[AnimationOperation alloc] initWithDuration:1.0 delay:0.0 options:0 animations:^{ viewToAnimate.center = point4; }]]; 

La subclass AnimationOperation podría parecerse a:

 // AnimationOperation.h #import <Foundation/Foundation.h> @interface AnimationOperation : NSOperation - (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations; @end 

y

 // AnimationOperation.m #import "AnimationOperation.h" @interface AnimationOperation () @property (nonatomic, readwrite, getter = isFinished) BOOL finished; @property (nonatomic, readwrite, getter = isExecuting) BOOL executing; @property (nonatomic, copy) void (^animations)(void); @property (nonatomic) UIViewAnimationOptions options; @property (nonatomic) NSTimeInterval duration; @property (nonatomic) NSTimeInterval delay; @end @implementation AnimationOperation @synthesize finished = _finished; @synthesize executing = _executing; - (instancetype)initWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations { self = [super init]; if (self) { _animations = animations; _options = options; _delay = delay; _duration = duration; } return self; } - (void)start { if ([self isCancelled]) { self.finished = YES; return; } self.executing = YES; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [UIView animateWithDuration:self.duration delay:self.delay options:self.options animations:self.animations completion:^(BOOL finished) { [self completeOperation]; }]; }]; } #pragma mark - NSOperation methods - (void)completeOperation { self.executing = NO; self.finished = YES; } - (BOOL)isConcurrent { return YES; } - (void)setExecuting:(BOOL)executing { if (_executing != executing) { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } } - (void)setFinished:(BOOL)finished { if (_finished != finished) { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } } @end 

En mi demostración, arriba, usé una queue en serie. Pero también podría usar una queue simultánea, pero use NSOperation dependencies de NSOperation para administrar la relación entre las diversas operaciones de animation. Muchas opciones aquí.


Si desea cancelar las animaciones, puede hacer algo como lo siguiente:

 CALayer *layer = [viewToAnimate.layer presentationLayer]; CGRect frame = layer.frame; // identify where it is now [animationQueue cancelAllOperations]; // cancel the operations [viewToAnimate.layer removeAllAnimations]; // cancel the animations viewToAnimate.frame = frame; // set the final position to where it currently is 

También podría incorporar esto en un método de cancel personalizado en la operación, si lo desea.

Parece que estás usando un semáforo para bloquear el hilo principal hasta que las animaciones compitan. Lamentablemente, el hilo principal es el hilo que realiza esas animaciones para que esto nunca ocurra.

No debe bloquear el hilo principal de su aplicación. Al hacerlo, evita que cualquier vista dibuje o responda a la input del usuario. También pronto activará un watchdog del sistema operativo que detecta que su aplicación no responde y la termina.

Las animaciones y sus bloques de finalización se expresan como operaciones asíncronas por una buena razón. Trate de abrazar eso en su layout.

Semaphore puede resolver todos los problemas de synchronization de bloques.

Dos puntos

  1. Necesita crear una queue de serie.

  2. dispatch_semaphore_wait y dispatch_semaphore_signal no pueden estar en la misma queue.


Aquí hay un ejemplo

 - (dispatch_queue_t)queue { if (!_queue) { _queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); } return _queue; } - (dispatch_semaphore_t)semaphore { if (!_semaphore) { _semaphore = dispatch_semaphore_create(0); } return _semaphore; } - (void)viewDidLoad { [super viewDidLoad]; [self funcA]; [self funcA]; [self funcA]; [self funcA]; [self funcA]; [self funcB]; } - (void)funcA { dispatch_async(self.queue, ^{ //Switch to main queue to perform animation dispatch_async(dispatch_get_main_queue(), ^{ self.view.alpha = 1; [UIView animateWithDuration:2 animations:^{ self.view.alpha = 0; } completion:^(BOOL finished) { NSLog(@"funcA completed"); //Unblock custom queue dispatch_semaphore_signal(self.semaphore); }]; }); //Block custom queue dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); }); } - (void)funcB { dispatch_async(self.queue, ^{ NSLog(@"funcB completed"); }); } 

Como dijo Jonah, no hay forma de hacer eso en el hilo principal. Si no quieres tener bloques nesteds, no hay nada malo con eso, pero entiendo tu deseo, simplemente coloca un método dentro del bloque y el bloque interno luego en el método. Si necesita cierres en el bloque interno, puede pasarlos como argumento.

Si lo hace, ampliará su amor a los bloques nesteds. D

Como muchas personas señalaron aquí, es cierto que la animation se ejecuta en el hilo principal y el uso del semáforo detiene el hilo principal. Pero esto aún se puede hacer con un semáforo usando este enfoque:

 // create semaphore dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ // do animations } completion:^(BOOL finished) { // send signal dispatch_semaphore_signal(semaphore); }]; // create background execution to avoid blocking the main thread dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // wait for the semaphore dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // now create a main thread execution dispatch_async(dispatch_get_main_queue(), ^{ // continue main thread logic }); });