GCDブロックで繰り返しタイマーを作成すると、なぜ機能しないのか疑問に思いました。
これは正常に機能します。
-(void)viewDidLoad{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
}
-(void)runTimer{
NSLog(@"hi");
}
しかし、これは機能しません。
dispatch_queue_t myQueue;
-(void)viewDidLoad{
[super viewDidLoad];
myQueue = dispatch_queue_create("someDescription", NULL);
dispatch_async(myQueue, ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
});
}
-(void)runTimer{
NSLog(@"hi");
}
NSTimers は現在のスレッドの 実行ループ でスケジュールされます。ただし、GCDディスパッチスレッドには実行ループがないため、GCDブロックでタイマーをスケジュールしても何も起こりません。
3つの合理的な選択肢があります。
+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]
_ を使用してタイマーを作成し、次に _-[NSRunLoop addTimer:forMode:]
_ を使用して、使用する実行ループで実際にタイマーをスケジュールします。これには、問題の実行ループにハンドルが必要ですが、メインスレッドで実行する場合は、 _+[NSRunLoop mainRunLoop]
_ を使用できます。dispatch_async()
タイマーを作成する前にメインキューに戻ります。これは、メイン実行ループを使用するオプション#1と同等です(メインスレッドにタイマーも作成されるため)。もちろん、ここでの本当の問題は、なぜ最初にGCDキューからタイマーを作成するのかということです。
NSTimerは、スレッドのrunloopにスケジュールされています。問題のコードでは、GCDによってディスパッチされたスレッドのrunloopが実行されていません。手動で開始する必要があり、実行ループを終了する方法が必要なので、NSTimerへの参照を保持し、適切なタイミングで無効にする必要があります。
NSTimerはターゲットを強く参照しているため、ターゲットはタイマーを強く参照できず、runloopはタイマーを強く参照しています。
weak var weakTimer: Timer?
func configurateTimerInBackgroundThread(){
DispatchQueue.global().async {
// Pause program execution in Xcode, you will find thread with this name
Thread.current.name = "BackgroundThreadWithTimer"
// This timer is scheduled to current run loop
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
// Start current runloop manually, otherwise NSTimer won't fire.
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
}
@objc func runTimer(){
NSLog("Timer is running in mainThread: \(Thread.isMainThread)")
}
将来タイマーが無効になった場合は、Xcodeでプログラムの実行を再度一時停止すると、スレッドがなくなっていることがわかります。
もちろん、GCDによってディスパッチされたスレッドにはrunloopがあります。 GCDは内部でスレッドを生成して再利用し、スレッドは呼び出し元に対して匿名です。安全に感じられない場合は、スレッドを使用できます。恐れることはありません、コードはとても簡単です。
実際、私は先週同じことを試みて、askerで同じ失敗をしました、そして私はこのページを見つけました。諦める前にNSThreadを試してみます。できます。では、なぜGCDのNSTimerが機能しないのでしょうか。そのはず。 runloopのドキュメント を読んで、NSTimerがどのように機能するかを確認してください。
NSThreadを使用してNSTimerを操作します。
func configurateTimerInBackgroundThread(){
let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil)
thread.name = "BackgroundThreadWithTimer"
thread.start()
}
@objc func addTimerInBackground() {
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}