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


84

一方でUIScrollView(またはその派生クラス)がスクロールしている、それがすべてのように思えるNSTimersスクロールが終了するまでのget一時停止を実行しています。

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



回答:


201

実装が簡単で簡単なソリューションは次のとおりです。

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

2
UITrackingRunLoopModeは、さまざまなタイマー動作を作成するために使用できるモード(およびそのパブリック)です。
トムアンデルセン

それが大好きで、GLKViewControllerでも魅力のように機能します。UIScrollViewが表示されたらcontroller.pausedをYESに設定し、説明に従って独自のタイマーを開始するだけです。スクロールビューが閉じられたら、それを元に戻します。私の更新/レンダリングループはそれほど高価ではないので、それが役立つかもしれません。
Jeroen Bouma 2013

3
驚くばかり!タイマーを無効にするかどうかにかかわらず、タイマーを削除する必要がありますか?ありがとう
Jacopo Penzo 2015

これはスクロールには機能しますが、アプリがバックグラウンドに入るとタイマーを停止します。両方を達成するための解決策はありますか?
プルキットシャルマ

23

Swift3を使用している人のために

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


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

7
これは、コールする方が良いtimer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)の代わりに、最初のコマンドとしてTimer.scheduleTimer()ので、scheduleTimer()実行ループのためにタイマーを追加し、次の呼び出しは、別のは、同じ実行ループになく、異なるモードで追加することです。同じ作業を2回行わないでください。
Accid Bright 2018

8

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

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

7

これは迅速なバージョンです。

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

6

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


11
別のスレッドは必要ありません。Kashifからの最新の回答を参照してください。私はそれを試しました、そしてそれは余分なスレッドを必要とせずに動作します。
progrmr 2011

3
スズメに大砲を撃ちます。スレッドの代わりに、正しい実行ループモードでタイマーをスケジュールするだけです。
uliwitness 2011

2
この問題を修正するために1行のコードを追加するだけでよいので、以下のKashifの回答が最善の回答だと思います。
ダミアンマーフィー。

3

誰でもSwift4を使用できます。

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

1

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


iOS開発者は必読です。最終的には、RunLoopを介して多くのことが実行されます。

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

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

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

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

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

実行ループが実行の途中でタイマーが起動するとどうなりますか?

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

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

スクロールなど、実行ループをビジー状態に保つものは、タイマーが起動するたびにシフトしますか?

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

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

できません。OSはあなたのために自分自身を変えるだけです。たとえば、ユーザーがタップすると、モードがに切り替わりますeventTracking。ユーザーのタップが終了すると、モードはに戻りdefaultます。特定のモードで何かを実行したい場合は、それが確実に行われるようにするのはあなた次第です。


解決:

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

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

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

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との話し合いからさらに深く掘り下げます:

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

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.