我有一种情况,我必须等待 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) 可以解决问题,但是当动画操作被推送到主线程时,函数立即返回并且锁将立即释放。
感谢您到目前为止的阅读。