Во время UIScrollView
прокрутки объекта (или его производного класса) кажется, что все NSTimers
выполняемые операции приостанавливаются до завершения прокрутки.
Есть ли способ обойти это? Потоки? Установка приоритета? Что-нибудь?
ios
uiscrollview
nstimer
Маккклин
источник
источник
Ответы:
Легкое и простое в реализации решение:
NSTimer *timer = [NSTimer timerWithTimeInterval:... target:... selector:.... userInfo:... repeats:...]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
источник
Для всех, кто использует Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true) RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
источник
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
в качестве первой команды вместоTimer.scheduleTimer()
, потому чтоscheduleTimer()
добавляет таймер к циклу выполнения, а следующий вызов - это еще одно добавление к тому же циклу выполнения, но с другим режимом. Не делайте одну и ту же работу дважды.Да, Пол прав, это проблема цикла выполнения. В частности, вам нужно использовать метод NSRunLoop:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
источник
Это быстрая версия.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true) NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
источник
Вам нужно запустить другой поток и еще один цикл выполнения, если вы хотите, чтобы таймеры срабатывали во время прокрутки; поскольку таймеры обрабатываются как часть цикла событий, если вы заняты обработкой прокрутки вашего представления, вы никогда не дойдете до таймеров. Хотя штраф за производительность / расход заряда батареи из-за запуска таймеров в других потоках может не иметь смысла в этом случае.
источник
для всех, кто использует Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true) RunLoop.main.add(timer, forMode: .common)
источник
tl; dr цикл выполнения выполняет прокрутку, поэтому он не может больше обрабатывать события - если вы вручную не установите таймер, чтобы это также могло произойти, когда цикл выполнения обрабатывает события касания. Или попробуйте альтернативное решение и используйте GCD
Обязательно к прочтению любому разработчику iOS. Многие вещи в конечном итоге выполняются через RunLoop.
Получено из документации Apple .
Что такое цикл выполнения?
Как нарушается доставка событий?
Что произойдет, если таймер сработает, когда цикл выполнения находится в середине выполнения?
Это происходит МНОГО РАЗ, а мы даже не замечаем этого. Я имею в виду, что мы установили таймер на 10: 10: 10: 00, но цикл выполнения выполняет событие, которое длится до 10: 10: 10: 05, поэтому таймер запускается 10: 10: 10: 06
Будет ли прокрутка или что-нибудь, что заставляет runloop быть занятым, постоянно сдвигать мой таймер?
Как я могу изменить режим RunLoops?
Вы не можете. ОС просто меняется для вас. например, когда пользователь нажимает, режим переключается на
eventTracking
. Когда пользовательские нажатия заканчиваются, режим возвращается вdefault
. Если вы хотите, чтобы что-то работало в определенном режиме, вы должны убедиться, что это произойдет.Решение:
Когда пользователь прокручивает, становится режим цикла выполнения
tracking
. RunLoop предназначен для переключения передач. Как только режим установленeventTracking
, он дает приоритет (помните, что у нас есть ограниченное количество ядер ЦП) сенсорным событиям. Это архитектурный проект дизайнеров ОС .По умолчанию таймеры НЕ устанавливаются в
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
Углубляясь в беседу с Даниэлем Ялкутом:
Вопрос: как GCD (фоновые потоки), например, asyncAfter в фоновом потоке, выполняется вне RunLoop? Насколько я понимаю, все должно выполняться в RunLoop.
Не обязательно - каждый поток имеет не более одного цикла выполнения, но может иметь ноль, если нет причин координировать выполнение «владения» потоком.
Потоки - это доступность на уровне ОС, которая дает вашему процессу возможность разделить свои функциональные возможности по нескольким контекстам параллельного выполнения. Циклы выполнения - это возможность на уровне фреймворка, которая позволяет дополнительно разделить один поток, чтобы он мог эффективно совместно использоваться несколькими путями кода.
Обычно, если вы отправляете что-то, что запускается в потоке, у него, вероятно, не будет цикла выполнения, если только что-то не вызовет, что
[NSRunLoop currentRunLoop]
неявно его создаст.Вкратце, режимы - это в основном механизм фильтрации для входов и таймеров.
источник