当所有代码都在主线程上时,如何优雅地锁定(寻找NSLock的替代方案)



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

为了提供一些使用它的上下文,我通过UIAttachmentBehavior(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秒后重试(大约是动画应该花费的时间)。

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

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

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

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

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

但我认为,你可能可以有状态变量来指示它是否需要做A和是否需要做C。最终产品应该是这样的:

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

我不确定你是否能理解我的意思,但这个想法可能是可中断和响应的交互以及A和C块的条件执行(即,如果这个动画没有中断前一个动画,只做A;如果这个动画不被后一个动画中断,只做C)。

最新更新