Выполнить повторение NSTimer с GCD?
Мне было интересно, почему, когда вы создаете повторяющийся таймер в блоке 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");
}
Ответы
Ответ 1
NSTimers запланированы в текущем потоке run loop, Однако потоки отправки GCD не имеют циклов запуска, поэтому таймеры планирования в блоке GCD ничего не сделают.
Там три разумных альтернативы:
- Выясните, какой цикл цикла вы планируете включить таймер, и явно это сделаете. Используйте
+[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:]
, чтобы создать таймер, а затем -[NSRunLoop addTimer:forMode:]
для фактического планирования его в цикле выполнения, который вы хотите использовать. Для этого требуется наличие дескриптора в заданном цикле, но вы можете просто использовать +[NSRunLoop mainRunLoop]
, если хотите сделать это в основном потоке.
- Переключитесь на использование источника отправки на основе таймера. Это реализует таймер в механизме, поддерживающем GCD, который будет запускать блок с интервалом, который вы хотите, в очереди по вашему выбору.
- Явно
dispatch_async()
вернуться к основной очереди перед созданием таймера. Это эквивалентно опции № 1, использующей цикл основного запуска (так как он также создает таймер в основном потоке).
Конечно, здесь возникает реальный вопрос: зачем вы создаете таймер из очереди GCD, чтобы начать?
Ответ 2
NSTimer запланирован для потоков runloop. В этом вопросе runloop потока, отправленного GCD, не запущен. Вы должны запустить его вручную, и должен быть выход из цикла выполнения, поэтому вы должны сохранить ссылку на 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 генерирует и повторно использует потоки внутри, потоки анонимны для вызывающего. Если вы не чувствуете себя в безопасности, вы можете использовать Thread. Не бойтесь, код очень прост.
На самом деле, я пробую то же самое на прошлой неделе и получаю то же самое с апеллятором, тогда я нашел эту страницу. Я пробую NSThread, прежде чем сдаться. Оно работает. Так почему NSTimer в GCD не может работать? Должен быть. Прочтите документ 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)
}
Ответ 3
Это плохая идея. Я собирался удалить этот ответ, но я оставил его здесь, чтобы другие не делали ту же ошибку, что и я.
Спасибо #Kevin_Ballard за указание на это.
Вы добавили бы только одну строку в свой пример, и она будет работать так же, как вы ее написали:
[[NSRunLoop currentRunLoop] run]
чтобы вы получили:
-(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];
[[NSRunLoop currentRunLoop] run]
});
}
Так как ваша очередь myQueue
содержит NSThread и содержит NSRunLoop, и поскольку код в dispatch_async
запущен, контекст этого NSThread, currentRunLoop
будет возвращать цикл остановленного запуска, связанный с потоком вашего очереди.