UIKit アニメーションが終了するのを待たなければならない状況があり、完了ブロックを使用して関連するファイナライズ コードを実行しています。ここで、メイン スレッドから関数を 2 回呼び出すと、エラーが発生する競合状態が発生する可能性があることに気付きました。単純な @synchronized(self) ロックを使用できませんでしたが、NSLock を使用する別の回避策があります。これに対するよりエレガントな解決策があるかどうか疑問に思っていました。
これが使用されるコンテキストを示すために、UIAttachmentBehaviour (UIKit ダイナミクス) を介して多数のビューを相互に接続し、いくつかの物理的なアニメーションを実行します。ビューをスワイプすると、ビューが置き換えられ、アニメーションはビューがスライドイン/スライドアウトするように見えます (単純な翻訳)。私のソリューションでは、添付ファイルの動作を削除します。そうしないと、物理的な添付ファイルがスライド ビューに従います。これは、私が望んでいるものではありません。
問題のあるコードは次のようになります。
- (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 と呼び、2 番目の呼び出しを 2A...2D と呼びましょう。通常の状況では、望ましい実行順序は次のようなものです。
1A
0.5 seconds delay
1B
1C
2A
0.5 seconds delay
2B
2C
ただし、handleGesture をプログラムで 2 回呼び出す場合の実行順序は次のとおりです。
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];
}
];
}
}
tryLock の代わりに lock を呼び出すと、コードがメイン スレッドで実行され、ブロックされるとアニメーションが終了しないため、デッドロックが発生することに注意してください。
つまり、handleGesture が呼び出されると、アニメーションが終了するまでロックされます。完了ブロックの間または後に関数が再度呼び出されると、ロックを取得しようとします。取得できない場合は、0.5 秒 (アニメーションの所要時間とほぼ同じ) 後に再試行します。
今、これは食事の哲学者の問題になる可能性があると感じています. 関数の先頭に @synchronized(self) を置けば問題は解決すると思ったのですが、アニメーション操作をメインスレッドにプッシュすると、関数はすぐに戻り、ロックはすぐに解除されます。
ここまで読んでくれてありがとう。