创build一个方法来执行animation,并在目标c中使用信号量等待完成

我想创build一个方法,使用UIView的“+ animateWithDuration:animation:完成”方法来执行animation,并等待完成。 我很清楚,我可以将通常会在完成块后出现的代码放在后面,但是我想避免这种情况,因为之后会有大量的代码,包括更多的animation,这会使我留下嵌套块。

我试图用一个信号量来实现这个方法,但是我不认为这是最好的方法,尤其是因为它实际上并不工作。 任何人都可以告诉我什么是我的代码错误,和/或达到相同目标的最佳方式是什么?

+(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); } 

我不知道我的代码有什么问题,但是当我调用如下所示的方法时,完成块从来没有运行,我最终陷入了僵局。

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

– – – – – – – – – – – – – – – – – – – – – -编辑 – – – ——————————————-

如果有人遇到类似的问题,您可能有兴趣查看我使用的代码。 它使用recursion来使用每个完成块,而不必嵌套每个函数调用。

 +(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]; }]; } } 

可以像这样使用

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

在iOS 7和更高版本中,通常会使用关键帧animation来实现这一效果。

例如,由四个独立animation组成的两秒钟animation序列,每个animation序列占用整个animation的25%,每个animation序列看起来像:

 [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]; 

在早期的iOS版本中,可以通过几种方法排列一系列animation,但我build议您避免在主线程中使用信号量。

一种方法是将animation封装在一个并发的NSOperation子类中,直到animation结束才完成。 然后,您可以将您的animation添加到您自己的自定义串行队列中:

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

AnimationOperation子类可能如下所示:

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

 // 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 

在我的演示中,我使用了一个串行队列。 但是你也可以使用并发队列,但是使用NSOperation依赖来pipe理各种animation操作之间的关系。 这里有很多select。


如果你想取消animation,你可以做如下的事情:

 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 

如果需要,也可以在操作中将其并入到自定义的cancel方法中。

你似乎正在使用信号来阻止主线程,直到animation竞争。 不幸的是,主线程是执行这些animation的线程,所以这绝不会发生。

你不能阻止你的应用程序的主线程。 这样做可以防止任何视图绘制或响应用户input。 你也将很快触发一个操作系统看门狗,它会检测到你的应用程序没有响应,并终止它。

animation完成块被表示为asynchronous操作。 尝试在你的devise中拥抱。

信号量可以解决所有的块同步问题。

两点

  1. 需要创build一个串行队列。

  2. dispatch_semaphore_waitdispatch_semaphore_signal不能在同一个队列中。


这是一个例子

 - (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"); }); } 

正如约拿所说,在主线上​​没有办法做到这一点。 如果你不想嵌套块,没有什么不好的,但是我理解你的愿望,只需要在块内部和内部块中的方法,然后在方法中。 如果你需要在内部块closures,你可以将它们作为parameter passing。

这样做会扩大你的爱嵌套块。 😉

正如很多人在这里指出的,animation运行在主线程上并且信号量的使用停止了主线程。 但是这仍然可以通过使用这种方法的信号量来完成:

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