Как создать NSTimer в фоновом streamе?

У меня есть задача, которая должна выполняться каждые 1 секунду. В настоящее время я запускаю NSTimer каждые 1 сек. Как у меня есть огонь по таймеру в фоновом streamе (не UI-stream)?

Я мог бы запустить NSTimer в основном streamе, а затем использовать NSBlockOperation для отправки фонового streamа, но мне интересно, есть ли более эффективный способ сделать это.

Таймер должен быть установлен в цикл запуска, работающий на уже запущенном фоновом streamе. Этот stream должен будет продолжать запускать цикл выполнения, чтобы запустить таймер. И для этого фонового streamа, чтобы продолжить возможность запуска других событий таймера, ему нужно будет создать новый stream, чтобы фактически обрабатывать события в любом случае (предполагая, конечно, что обработка, которую вы делаете, занимает значительное количество времени).

Что бы это ни стоило, я думаю, что обработка событий таймера, NSBlockOperation новый stream с помощью Grand Central Dispatch или NSBlockOperation вполне разумно использует ваш основной stream.

Если вам это нужно, таймеры все еще запускаются при прокрутке ваших представлений (или карт), вам нужно назначить их в другом режиме цикла. Замените текущий таймер:

 [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; 

С этим:

 NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 

Подробнее см. В этом сообщении в блоге: отслеживание событий останавливается NSTimer

EDIT: второй блок кода, NSTimer по-прежнему работает в основном streamе, все еще в том же цикле запуска, что и scrollviews. Разница заключается в режиме цикла запуска. Проверьте сообщение в блоге, чтобы получить четкое объяснение.

Если вы хотите пойти чистым GCD и использовать источник отправки, у Apple есть пример кода для этого в своем руководстве по программированию параллелизма :

 dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); if (timer) { dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); dispatch_source_set_event_handler(timer, block); dispatch_resume(timer); } return timer; } 

Swift 3:

 func createDispatchTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval, queue: DispatchQueue, block: @escaping ()->()) -> DispatchSourceTimer { let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0), queue: queue) timer.scheduleRepeating(deadline: DispatchTime.now(), interval: interval, leeway: leeway) // Use DispatchWorkItem for compatibility with iOS 9. Since iOS 10 you can use DispatchSourceHandler let workItem = DispatchWorkItem(block: block) timer.setEventHandler(handler: workItem) timer.resume() return timer } 

Затем вы можете настроить односекундное событие таймера, используя следующий код:

 dispatch_source_t newTimer = CreateDispatchTimer(1ull * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Repeating task }); 

конечно, чтобы сохранить и отпустить свой таймер, когда это будет сделано, конечно. Вышеизложенное дает вам 1/10-ю вторую свободу действий при стрельбе этих событий, которые вы могли бы затянуть, если хотите.

Это должно работать,

Он повторяет метод каждые 1 секунду в фоновом режиме без использования NSTimers 🙂

 - (void)methodToRepeatEveryOneSecond { // Do your thing here // Call this method again using GCD dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); double delayInSeconds = 1.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, q_background, ^(void){ [self methodToRepeatEveryOneSecond]; }); } 

Если вы находитесь в основной очереди и хотите вызвать метод выше, вы можете сделать это, чтобы он менялся в фоновом режиме, прежде чем запускаться 🙂

 dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_async(q_background, ^{ [self methodToRepeatEveryOneSecond]; }); 

Надеюсь, поможет

Для быстрого 3.0,

Ответ Тихонва не слишком объясняет. Здесь добавляется кое-что из моего понимания.

Чтобы сделать вещи короткими, вот код. Это отличается от кода Тихонва в том месте, где я создаю таймер. Я создаю таймер с помощью конструктора и добавляю его в цикл. Я думаю, что функция schedTimer добавит таймер в RunLoop основного streamа. Поэтому лучше создать таймер с помощью конструктора.

 class RunTimer{ let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent) let timer: Timer? private func startTimer() { // schedule timer on background queue.async { [unowned self] in if let _ = self.timer { self.timer?.invalidate() self.timer = nil } let currentRunLoop = RunLoop.current self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true) currentRunLoop.add(self.timer!, forMode: .commonModes) currentRunLoop.run() } } func timerTriggered() { // it will run under queue by default debug() } func debug() { // print out the name of current queue let name = __dispatch_queue_get_label(nil) print(String(cString: name, encoding: .utf8)) } func stopTimer() { queue.sync { [unowned self] in guard let _ = self.timer else { // error, timer already stopped return } self.timer?.invalidate() self.timer = nil } } } 

Создать очередь

Сначала создайте очередь для запуска таймера на фоновом режиме и сохраните эту очередь как свойство classа, чтобы повторно использовать ее для таймера остановки. Я не уверен, что нам нужно использовать одну и ту же очередь для запуска и остановки, поэтому я сделал это потому, что увидел здесь предупреждающее сообщение.

Класс RunLoop обычно не считается streamобезопасным, и его методы следует вызывать только в контексте текущего streamа. Вы никогда не должны пытаться вызвать методы объекта RunLoop, работающие в другом streamе, так как это может привести к неожиданным результатам.

Поэтому я решил сохранить очередь и использовать ту же очередь для таймера, чтобы избежать проблем с синхронизацией.

Также создайте пустой таймер и сохраните его в переменной classа. Сделайте это необязательным, чтобы остановить таймер и установить его на нуль.

 class RunTimer{ let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent) let timer: Timer? } 

Таймер запуска

Чтобы запустить таймер, сначала вызовите async из DispatchQueue. Тогда лучше проверить, действительно ли таймер уже запущен. Если переменная таймера не равна нулю, тогда invalidate () и установите ее на нуль.

Следующий шаг – получить текущий RunLoop. Поскольку мы сделали это в созданном блоке очереди, он получит RunLoop для ранее созданной очереди фона.

Создайте таймер. Здесь вместо использования schedTimer мы просто вызываем конструктор таймера и передаем любое свойство, которое вы хотите для таймера, например timeInterval, target, selector и т. Д.

Добавьте созданный таймер в RunLoop. Запустить его.

Вот вопрос о запуске RunLoop. Согласно документации здесь, он говорит, что эффективно начинает бесконечный цикл, который обрабатывает данные из входных источников и таймеров цикла цикла.

 private func startTimer() { // schedule timer on background queue.async { [unowned self] in if let _ = self.timer { self.timer?.invalidate() self.timer = nil } let currentRunLoop = RunLoop.current self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true) currentRunLoop.add(self.timer!, forMode: .commonModes) currentRunLoop.run() } } 

Таймер запуска

Реализуйте функцию как обычно. Когда эта функция вызывается, она вызывается в очереди по умолчанию.

 func timerTriggered() { // under queue by default debug() } func debug() { let name = __dispatch_queue_get_label(nil) print(String(cString: name, encoding: .utf8)) } 

Вышеупомянутая функция отладки используется для печати имени очереди. Если вы когда-нибудь волновались, если он работает в очереди, вы можете позвонить ему, чтобы проверить.

Остановить таймер

Таймер остановки прост, вызывают проверку () и задают переменную таймера, хранящуюся внутри classа, равной нулю.

Здесь я снова запускаю его под очередью. Из-за предупреждения здесь я решил запустить весь код, связанный с таймером, в очереди, чтобы избежать конфликтов.

 func stopTimer() { queue.sync { [unowned self] in guard let _ = self.timer else { // error, timer already stopped return } self.timer?.invalidate() self.timer = nil } } 

Вопросы, связанные с RunLoop

Я немного немного смущен, если нам нужно вручную остановить RunLoop или нет. Согласно документации здесь, кажется, что когда к ней не подключены таймеры, она немедленно выйдет. Поэтому, когда мы останавливаем таймер, он должен существовать сам. Однако в конце этого документа он также сказал:

удаление всех известных источников входного сигнала и таймеров из цикла запуска не является гарантией выхода из цикла запуска. macOS может устанавливать и удалять дополнительные источники входных данных по мере необходимости для обработки запросов, направленных на stream получателя. Поэтому эти источники могут помешать запуску цикла.

Я попробовал решение ниже, приведенное в документации для гарантии прекращения цикла. Тем не менее, таймер не срабатывает после того, как я изменил .run () на код ниже.

 while (self.timer != nil && currentRunLoop.run(mode: .commonModes, before: Date.distantFuture)) {}; 

Я думаю, что это может быть безопасно для использования .run () в iOS. Поскольку в документации указано, что macOS устанавливает и удаляет дополнительные источники ввода, необходимые для обработки запросов, направленных на stream получателя. Так что iOS может быть в порядке.

Мое решение Swift 3.0 для iOS 10+, timerMethod() будет timerMethod() в фоновом режиме.

 class ViewController: UIViewController { var timer: Timer! let queue = DispatchQueue(label: "Timer DispatchQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil) override func viewDidLoad() { super.viewDidLoad() queue.async { [unowned self] in let currentRunLoop = RunLoop.current let timeInterval = 1.0 self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.timerMethod), userInfo: nil, repeats: true) self.timer.tolerance = timeInterval * 0.1 currentRunLoop.add(self.timer, forMode: .commonModes) currentRunLoop.run() } } func timerMethod() { print("code") } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) queue.sync { timer.invalidate() } } } 

Только Swift (хотя, вероятно, он может быть изменен для использования с Objective-C)

Проверьте DispatchTimer с https://github.com/arkdan/ARKExtensions , который «Выполняет закрытие указанной очереди отправки с указанными временными интервалами за указанное количество раз (необязательно)».

 let queue = DispatchQueue(label: "ArbitraryQueue") let timer = DispatchTimer(timeInterval: 1, queue: queue) { timer in // body to execute until cancelled by timer.cancel() } 

Сегодня, спустя 6 лет, я пытаюсь сделать то же самое, вот альтернатива: GCD или NSThread.

Таймеры работают вместе с циклами запуска, runloop streamа может быть получен только из streamа, поэтому ключ – это таймер расписания в streamе.

За исключением runloop основной нити, runloop должен запускаться вручную; должны быть некоторые события, которые нужно обрабатывать при запуске runloop, например Timer, иначе runloop завершит работу, и мы сможем использовать это для выхода из runloop, если таймер является единственным источником события: недействительным таймер.

Следующий код – Swift 4:

Решение 0: GCD

 weak var weakTimer: Timer? @objc func timerMethod() { // vefiry whether timer is fired in background thread NSLog("It's called from main thread: \(Thread.isMainThread)") } func scheduleTimerInBackgroundThread(){ DispatchQueue.global().async(execute: { //This method schedules timer to current runloop. self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true) //start runloop manually, otherwise timer won't fire //add timer before run, otherwise runloop find there's nothing to do and exit directly. RunLoop.current.run() }) } 

Таймер имеет сильную ссылку на цель, и runloop имеет сильную ссылку на таймер, после того, как таймер недействителен, он освобождает цель, поэтому держите слабую ссылку на нее в целевом объекте и не выполняйте ее в соответствующее время, чтобы выйти из runloop (а затем выйти из streamа).

Примечание: в качестве оптимизации функция sync DispatchQueue вызывает блок в текущем streamе. Фактически, вы выполняете над кодом в основном streamе, Timer запускается в основном streamе, поэтому не используйте функцию sync , иначе таймер не запускается в нужном вам streamе.

Вы можете назвать stream для отслеживания его активности, приостановив выполнение программы в Xcode. В GCD используйте:

 Thread.current.name = "ThreadWithTimer" 

Решение 1: Thread

Мы могли бы напрямую использовать NSThread. Не бойтесь, код прост.

 func configurateTimerInBackgroundThread(){ // Don't worry, thread won't be recycled after this method return. // Of course, it must be started. let thread = Thread.init(target: self, selector: #selector(addTimer), object: nil) thread.start() } @objc func addTimer() { weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true) RunLoop.current.run() } 

Решение 2: Тема подclassа

Если вы хотите использовать подclass Thread:

 class TimerThread: Thread { var timer: Timer init(timer: Timer) { self.timer = timer super.init() } override func main() { RunLoop.current.add(timer, forMode: .defaultRunLoopMode) RunLoop.current.run() } } 

Примечание: не добавляйте таймер в init, иначе таймер добавляет в runloop streamа вызывающего streamа init, а не runloop этого streamа, например, вы запускаете следующий код в основном streamе, если TimerThread добавляет таймер в методе init, таймер будет назначен на main нить runwop, а не runTool прокрутки таймера. Вы можете проверить его в timerMethod() .

 let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true) weakTimer = timer let timerThread = TimerThread.init(timer: timer) timerThread.start() 

PS О Runloop.current.run() , его документ предлагает не вызывать этот метод, если мы хотим, чтобы runloop завершилась, используйте run(mode: RunLoopMode, before limitDate: Date) , на самом деле run() повторно вызывают этот метод в NSDefaultRunloopMode , какой режим? Подробнее в runloop и thread .

 class BgLoop:Operation{ func main(){ while (!isCancelled) { sample(); Thread.sleep(forTimeInterval: 1); } } } 

Если вы хотите, чтобы ваш NSTimer работал в фоновом режиме, выполните следующие действия:

  1. вызвать метод [self beginBackgroundTask] в методах applicationWillResignActive
  2. вызов метода [self endBackgroundTask] в приложенииWillEnterForeground

это оно

 -(void)beginBackgroundTask { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundTask]; }]; } -(void)endBackgroundTask { [[UIApplication sharedApplication] endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; } 
  • Как получить доступ к первому свойству объекта в Javascript?
  • NSString для CFStringRef и CFStringRef для NSString в ARC?
  • self.variable и переменная разница
  • хранить и извлекать объект classа по общим предпочтениям
  • Сохранение пользовательских атрибутов в NSAttributedString
  • Как получить ключ в объекте JavaScript по его значению?
  • Как преобразовать значение NSString в NSData?
  • Интервью: Можем ли мы создать абстрактный class?
  • Преобразование документов Mongoose в json
  • Получить определенный объект из файла Rdata
  • В чем разница между переменной, объектом и ссылкой?
  • Давайте будем гением компьютера.