1

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) を置けば問題は解決すると思ったのですが、アニメーション操作をメインスレッドにプッシュすると、関数はすぐに戻り、ロックはすぐに解除されます。

ここまで読んでくれてありがとう。

4

1 に答える 1

1

NSLockメインスレッドでのロックの種類 (または@synchronizedセマフォなど)はお勧めしません。

理論的には、ここに示すようNSOperationにアニメーションを非同期サブクラスでラップできます。

率直に言って、中断可能で応答性の高いアニメーションのために iOS 8 に注がれたすべてのハードワークを考えると、これは少し残念なことのように思えます。WWDC 2014 のビデオ、中断可能で応答性の高い相互作用の構築を参照してください。新しいアニメーションを前のアニメーションの途中で開始する場合、アニメーションが終了するのを待ったり中断したりするのではなく、最初のアニメーションが現在ある場所からアニメーションをスムーズに取得するという考え方です。具体的な提案をするために、あなたのケースで望ましい最終的な UX に従っているわけではありませんが、そのビデオを見て、何かアイデアが生まれるかどうかを確認する価値があるかもしれません。

しかし、A を実行する必要があるかどうか、および C を実行する必要があるかどうかを示す状態変数を持つことができるかもしれないと考えています。最終製品は次のようになります。

1A
1B
(1Bアニメーションの途中で中断)
(2Aをスキップ)
2B は 1B が中断したところから再開します
(1Cスキップ)
2C

私が言っていることを理解できるかどうかはわかりませんが、アイデアは中断可能で応答性の高い相互作用と、A ブロックと C ブロックの条件付き実行である可能性があります (つまり、このアニメーションが前のアニメーションを中断しなかった場合にのみ A を実行します。このアニメーションが後続のアニメーションによって中断されなかった場合は C を実行します)。

于 2015-04-17T02:07:48.740 に答える