1

我有一种情况,我必须等待 UIKit 动画完成,并且我正在使用完成块来执行相关的最终代码。现在我意识到当我从主线程调用该函数两次时,我可以触发一个引入错误的竞争条件。我无法使用简单的@synchronised(self) 锁,但我使用 NSLock 有不同的解决方法。我想知道是否有更优雅的解决方案。

为了给出使用它的一些上下文,我通过 UIAttachmentBehaviour (UIKit Dynamics) 将许多视图相互连接,以进行一些物理动画。当我滑动视图时,它被替换并且动画看起来像视图正在滑入/滑出(简单翻译)。在我的解决方案中,我删除了附件行为,否则物理附件将跟随滑动视图,这不是我想要的。

有问题的代码如下所示:

- (void)handleGesture;
{
        // [A]
        // remove UIAttachmentBehaviours (UIKit dynamics) between the static views and the view that fades out
        ...
        // animate translation of fade-out and fade-in views
        [UIView animateWithDuration:0.5
                         animations:^{
                             // [B]
                             for (UIView* view in swipeAllViews)
                             {
                                CGPoint location = view.center;
                                location.x += 2*screenWidth;
                                view.center = location;
                             }
                         }
                         completion:^(BOOL finished) {
                             // [C]
                             for (UIView* view in swipeOutViews)
                             {
                                 [view removeFromSuperview];
                             }

                             for (UIView* view in swipeInViews)
                             {
                                 // do some setup
                             }

                             // add UIAttachmentBehaviour between the static views and the new view that fades in
                        }
        ];

        }
}

请注意,手动触发问题很困难,但如果您以编程方式调用代码片段,则执行顺序会略有不同。为了给出一个想法,我用 A、B、C、D 标记了代码部分。让我们调用第一个执行行 1A...1D 和第二个调用 2A...2D 的跟踪。在正常情况下,所需的执行顺序类似于:

1A
0.5 seconds delay
1B
1C
2A
0.5 seconds delay
2B
2C

但是,当以编程方式调用 handleGesture 两次时,执行顺序是:

1A
2A
0.5 seconds delay
1B
1C
2B
2C

我想出的解决方法如下所示:

- (void)viewDidLoad;
{
    self.theLock = [[NSLock alloc] init];
}

- (void)handleGesture;
{
    if (![self.theLock tryLock])
    {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
                       dispatch_get_main_queue(), ^(void){
                           [self handleGesture];
        });
    }
    else
    {
        // remove some UIAttachmentBehaviours (UIKit dynamics)
        ...

        // animate translation of views
        [UIView animateWithDuration:0.5
                         animations:^{
                             for (UIView* view in swipeAllViews)
                             {
                                CGPoint location = view.center;
                                location.x += 2*screenWidth;
                                view.center = location;
                             }
                         }
                         completion:^(BOOL finished) {

                             for (UIView* view in swipeOutViews)
                             {
                                 [view removeFromSuperview];
                             }

                             for (UIView* view in swipeInViews)
                             {
                                 // do some setup
                             }

                             // add UIAttachmentBehaviours between the old and new views

                             [self.theLock unlock];
                         }
        ];

        }
}

注意,如果你调用 lock 而不是 tryLock,那么你最终会陷入死锁,因为代码是在主线程上执行的,如果它阻塞了动画将永远不会完成。

换句话说,当handleGesture 被调用时,它会被锁定直到动画结束。如果在完成块之间或之后再次调用该函数,它会尝试获取锁。如果它无法获取它,它会在 0.5 秒后再次尝试(大约是动画应该花费的时间)。

现在我觉得这可能会成为餐饮哲学家的问题,这让我想知道是否没有更简单的解决方案。我认为在函数开头放置一个@synchronised(self) 可以解决问题,但是当动画操作被推送到主线程时,函数立即返回并且锁将立即释放。

感谢您到目前为止的阅读。

4

1 回答 1

1

我不建议在主线程上使用任何类型的锁(NSLock或或信号量或任何东西)。@synchronized

理论上,您可以将动画包装在异步NSOperation子类中,如下所示

坦率地说,考虑到他们在 iOS 8 中为可中断和响应式动画付出的所有努力,这一切似乎有点可惜。请参阅 WWDC 2014 视频,构建可中断和响应式交互。这个想法是,如果您在前一个动画的中途开始一个新动画,让它从当前第一个动画的位置平滑地拾取动画,而不是等待它完成或以其他方式中断它。我没有按照您的情况下想要的最终用户体验来提出具体建议,但可能值得观看该视频,看看它是否会为您产生任何想法。

但我认为您可能能够拥有指示它是否需要执行 A 以及是否需要执行 C 的状态变量。最终产品将类似于:

1A
1B
(1B动画中途中断)
(跳过 2A)
2B 从 1B 停止的地方接起
(跳过 1C)
2C

我不确定你是否能听懂我说的,但这个想法可能是可中断的和响应式的交互以及 A 和 C 块的有条件执行(即仅在此动画没有中断前一个动画时才执行 A;仅如果此动画没有被后续动画打断,则执行 C)。

于 2015-04-17T02:07:48.740 回答