web-dev-qa-db-ja.com

UIScrollViewは、スクロールが完了するまでNSTimerを一時停止します

UIScrollView(またはその派生クラス)がスクロールしている間、実行中のすべてのNSTimersはスクロールが完了するまで一時停止しているようです。

これを回避する方法はありますか?スレッド?優先設定?何か?

83
mcccclean

ソリューションを実装するのは簡単でシンプルです:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
198
Kashif Hisam

Swift 3

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
23
Isaac

はい、ポールは正解です。これは実行ループの問題です。具体的には、NSRunLoopメソッドを使用する必要があります。

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
8
August

これはSwiftバージョンです。

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
7
Andrew Cook

スクロール中にタイマーを起動する場合は、別のスレッドと別の実行ループを実行する必要があります。タイマーはイベントループの一部として処理されるため、ビューのスクロールの処理でビジー状態の場合、タイマーを回避することはできません。ただし、他のスレッドでタイマーを実行することによるパフォーマンス/バッテリーのペナルティは、このケースを処理する価値がない場合があります。

6
Ana Betts

誰でも使用Swift 4:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)
3
YuLong Xiao

tl; dr runloopがスクロールを実行しているため、これ以上イベントを処理できません—タイマーを手動で設定して、runloopがタッチイベントを処理しているときにも発生しないようにした場合を除きます。または別のソリューションを試してGCDを使用する


IOS開発者は必読です。多くのことは、最終的にはRunLoopを通じて実行されます。

Appleのドキュメント から派生。

実行ループとは何ですか?

実行ループは、その名前のとおりです。これは、スレッドが入り、着信イベントに応答してイベントハンドラーを実行するために使用するループです。

イベントの配信はどのように中断されますか?

実行ループを実行するとタイマーやその他の定期的なイベントが配信されるため、そのループを回避すると、これらのイベントの配信が中断されます。この動作の典型的な例は、ループに入り、アプリケーションから繰り返しイベントを要求することにより、マウス追跡ルーチンを実装するたびに発生します。コードがイベントを直接取得しているため、アプリケーションにイベントを正常にディスパッチさせるのではなく、マウストラッキングルーチンが終了してアプリケーションに制御を返すまで、アクティブタイマーは起動できません。

実行ループが実行中にタイマーが発生するとどうなりますか?

これは、私たちが気づくことなく、多くの時間に起こります。つまり、タイマーを10:10:10:00に起動するように設定しましたが、runloopは10:10:10:05までかかるイベントを実行しているため、タイマーは10:10:10:06に起動されます。

同様に、実行ループがハンドラールーチンの実行中にタイマーが起動した場合、タイマーは、次回実行ループを介してハンドラールーチンが呼び出されるまで待機します。実行ループがまったく実行されていない場合、タイマーは起動しません。

スクロールまたはrunloopをビジー状態にしておくものは、タイマーが作動するたびにシフトしますか?

イベントを1回だけまたは繰り返し生成するようにタイマーを設定できます。繰り返しタイマーは、実際の発砲時刻ではなく、スケジュールされた発砲時刻に基づいて自動的に再スケジュールします。たとえば、タイマーが特定の時間に起動され、その後5秒ごとに起動するようにスケジュールされている場合、実際の起動時間が遅れても、スケジュールされた起動時間は常に元の5秒の時間間隔になります。発射時間が非常に遅れて、1つ以上のスケジュールされた発射時刻を逃した場合、タイマーは、欠落した期間に1回だけ発火します。逃した期間の発砲後、タイマーは次のスケジュールされた発砲時刻に再スケジュールされます。

RunLoopsのモードを変更するにはどうすればよいですか?

できません。 OSは自動的に変化します。例えばユーザーがタップすると、モードがeventTrackingに切り替わります。ユーザーのタップが終了すると、モードはdefaultに戻ります。特定のモードで何かを実行したい場合は、それを確実に行う必要があります。


解決:

ユーザーがスクロールすると、実行ループモードは tracking になります。 RunLoopはギアをシフトするように設計されています。モードがeventTrackingに設定されると、タッチイベントが優先されます(CPUコアが限られていることに注意してください)。 これは、OSデザイナー によるアーキテクチャ設計です。

デフォルトでは、タイマーはtrackingモードでスケジュールされていません。彼らはに予定されています:

タイマーを作成し、現在の実行ループでdefaultモードでスケジュールします。

下の scheduledTimer はこれを行います:

RunLoop.main.add(timer, forMode: .default)

スクロール時にタイマーを機能させるには、次のいずれかを実行する必要があります。

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

または単に:

RunLoop.main.add(timer, forMode: .common)

最終的に上記のいずれかを実行すると、タッチイベントによってスレッドがブロックされないことになります。これは次と同等です:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

代替ソリューション:

タイマーにGCDを使用すると、実行ループ管理の問題からコードを「シールド」できます。

繰り返さない場合は、次のように使用します。

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

繰り返しタイマーを使用する場合:

DispatchSourceTimer の使用方法を参照してください


Daniel Jalkutとのディスカッションからさらに掘り下げる:

質問:GCD(バックグラウンドスレッド)はどのように実行されますか?バックグラウンドスレッドのasyncAfterがRunLoopの外部で実行されますか?これからの私の理解は、すべてがRunLoop内で実行されることです

必ずしもそうとは限りません-すべてのスレッドには最大で1つの実行ループがありますが、スレッドの実行「所有権」を調整する理由がない場合はゼロにすることができます。 (編集)

スレッドはOSレベルのアフォーダンスであり、プロセスがその機能を複数の並列実行コンテキストに分割できるようにします。実行ループはフレームワークレベルのアフォーダンスであり、単一のスレッドをさらに分割して、複数のコードパスで効率的に共有できるようにします。

通常、スレッドで実行されるものをディスパッチする場合、何かが[NSRunLoop currentRunLoop]を呼び出して暗黙的に作成する場合を除いて、実行ループはおそらくありません。

一言で言えば、モードは基本的に入力とタイマーのフィルターメカニズムです。

0
Honey