3

ある段階で一連の行を追加または削除する UITableView を使用して iOS アプリを開発しています。行数が多いため、この操作には時間がかかる場合があります。ただし、時間がかかるかどうかは簡単には判断できません。

この操作に時間がかかる場合にのみ、UIActivityIndi​​cator (スピナー) を表示したいと考えています。私がいつもこれを行ってきた方法は、時間のかかる操作を開始し、ある程度の遅延 (0.5 秒など) の後、操作がまだ実行されているかどうかをテストし、実行されている場合は、UIActivityIndi​​cator の表示を開始します。

時間のかかる操作をバックグラウンド スレッドで実行できる場合、これは問題ありません。ただし、この特定のケースは注意が必要です。これは、長い操作 (deleteRowsAtIndexPaths:withRowAnimation:) をメイン スレッドで実行する必要があるためです (このメソッドをバックグラウンド スレッドで実行すると、バックグラウンド スレッドが UI を更新しようとすると、アプリがクラッシュすることがあります)。

私が試した最新のことは、次の行にありました。

- (void) manipulateTableView
{
    stillBusy = YES;
    [self performSelectorInBackground:@selector(waitBeforeShowingBusyIndicatorForView:) withObject:view];
    .
    .
    .
    [self performSelectorOnMainThread:@selector(deleteRowsAtIndexPathsWithRowAnimation:) withObject:args waitUntilDone:YES];
    stillBusy = NO;
}

- (void) waitBeforeShowingBusyIndicatorForView:(UIView*) view
{
    usleep((int) (BUSY_INDICATOR_DELAY * 1000000));
    if (stillBusy)
        [self performSelectorOnMainThread:@selector(showBusyIndicatorForView:) withObject:view waitUntilDone:NO];
}

showBusyIndi​​catorForView: は UI を操作するため、メイン スレッドで呼び出す必要があります。そうしないと、アプリがクラッシュする可能性があります。

deleteRowsAtIndexPaths:withRowAnimation: に非常に長い時間がかかる場合、waitBeforeShowingBusyIndi​​catorForView: の遅延が期限切れになり、performSelectorOnMainThread:... メソッドが呼び出されてすぐに戻ります。しかし、その後 showBusyIndi​​catorForView: メソッドが呼び出されるのは、deleteRowsAtIndexPaths:withRowAnimation: への呼び出しが完了した後であるため、目的に反します

なぜこれが起こるのか理解できたと思います。deleteRowsAtIndexPaths:withRowAnimation: メソッドはメイン ループの繰り返しで実行され、実行中は showBusyIndi​​catorForView: の呼び出しがメイン ループのメッセージとしてキューに入れられます。メイン ループが deleteRowsAtIndexPaths:withRowsAnimation: の実行を完了した後でのみ、次のメッセージのキューをポーリングし、showBusyIndi​​catorForView: の実行を開始します。

これを適切に機能させる方法はありますか。メインループを中断して、すぐに showBusyIndi​​catorForView: を実行させることは可能でしょうか?

4

3 に答える 3

3

次の 2 つのオプションが思い浮かびます。

  1. 長時間実行されるフォアグラウンド ルーチンを変更して、複数のブロックに分割し、プロセスの各ステップを別々にメイン キューに繰り返しディスパッチします。そして、遅いフォアグラウンド タスクの各部分をディスパッチし、前の部分が完了したときにのみ次のステップをメイン キューにディスパッチするようにプロセスを設計するようにします。(メイン キューに大量のプロセスをキューに入れるだけではありません。) そうすれば、スピナーがキューに入れられたときに、他のジョブにぴったりとはまるチャンスが得られます。削除をアニメーション化している場合、それは不可能かもしれませんが、繰り返しになりますが、単一のdeleteRowsAtIndexPaths.

  2. おそらく、更新にそれほど時間がかかっている場合は、その理由を尋ねたほうがよいでしょう。それdeleteRowsAtIndexPathsを説明するべきではありません。具体的には、 multipledeleteRowsAtIndexPathsを発行するのではなく、NSMutableArray削除する行を作成してから、1 回の UI 呼び出しでそれらを削除します。998 行 (基になるモデル オブジェクトを含む) を削除したところ、ほぼ瞬時に削除されました。

可能であれば、オプション2に傾倒します。

于 2013-01-15T22:50:14.953 に答える
0

メイン スレッドで長時間の操作が実行されている間は、UI を更新できません。

遅延の原因が実際にある場合-deleteRowsAtIndexPaths:withRowAnimation:(数千行の削除をアニメーション化するようにテーブル ビューに要求している場合)、最良の解決策は、特定の (比較的小さい) しきい値を超えてアニメーションを回避することです。-reloadData代わりに電話してください。

于 2013-01-15T23:03:14.343 に答える
0

ロブ、アドバイスありがとう!解決策を見つけるのに役立ちました!

まず第一に、記録のために、私はすべての行に対して一度だけ deleteRowsAtIndexPaths を呼び出していました (数千の行:O!)。

私が行った重要な観察は、アニメーションの開始時に少数の行しか画面に表示されないということでした。行の削除は、そのセクションのヘッダーをタップするとトリガーされるため、特にセクションの一番上の行です。このため、すべての行がアニメーションの一部である必要はありません。最初にすべての非表示行を (アニメーションなしで) 削除し、次にアニメーションを使用して表示行のみを削除できます。

次に、deleteRowsAtIndexPaths を 2 回呼び出しました。最初の呼び出しで非表示の行がすべて削除され、2 回目の呼び出しで表示されている (上部の) 行が削除されました。これで、deleteRowsAtIndexPaths の最初の呼び出しに非常に長い時間がかかりました。これは、何千もの行を削除する必要があり、再びアクティビティ インジケーター ビューがアニメーション化を開始する機会を得ることができなかったためです。次に、行削除の最初のフェーズを、次のように一度に 200 行を削除するループに変更しました。

for ( /* next range of invisible rows */ )
{
    // Compute index paths of the bottom 200 rows.
    [self performSelectorOnMainThread:@selector(deleteRowsAtIndexPathsWithRowAnimation:) withObject:args waitUntilDone:YES];
}

アクティビティ インジケーター アニメーションを開始するメソッドは、deleteRowsAtIndexPaths…: への 2 つの後続の呼び出しの間でキューに入れられます。

興味深いことに、一度に 200 行を削除しているので、かなりの時間がかかりますが、一度にすべてを削除する場合よりもはるかに高速です。:|

于 2013-01-16T18:43:54.697 に答える