Во время UIScrollView
прокрутки объекта (или его производного класса) кажется, что все NSTimers
выполняемые операции приостанавливаются до завершения прокрутки.
Есть ли способ обойти это? Потоки? Установка приоритета? Что-нибудь?
Во время UIScrollView
прокрутки объекта (или его производного класса) кажется, что все NSTimers
выполняемые операции приостанавливаются до завершения прокрутки.
Есть ли способ обойти это? Потоки? Установка приоритета? Что-нибудь?
Ответы:
Легкое и простое в реализации решение:
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()
добавляет таймер к циклу выполнения, а следующий вызов - это еще одно добавление к тому же циклу выполнения, но с другим режимом. Не делайте одну и ту же работу дважды.
Вам нужно запустить другой поток и еще один цикл выполнения, если вы хотите, чтобы таймеры срабатывали во время прокрутки; поскольку таймеры обрабатываются как часть цикла событий, если вы заняты обработкой прокрутки вашего представления, вы никогда не дойдете до таймеров. Хотя штраф за производительность / расход заряда батареи из-за запуска таймеров в других потоках может не иметь смысла в этом случае.
tl; dr цикл выполнения выполняет прокрутку, поэтому он не может больше обрабатывать события - если вы вручную не установите таймер, чтобы это также могло произойти, когда цикл выполнения обрабатывает события касания. Или попробуйте альтернативное решение и используйте GCD
Обязательно к прочтению любому разработчику iOS. Многие вещи в конечном итоге выполняются через RunLoop.
Получено из документации Apple .
Цикл выполнения очень похож на свое название. Это цикл, в который входит ваш поток и который он использует для запуска обработчиков событий в ответ на входящие события.
Поскольку таймеры и другие периодические события доставляются при запуске цикла выполнения, обход этого цикла нарушает доставку этих событий. Типичный пример такого поведения возникает всякий раз, когда вы реализуете процедуру отслеживания мыши, входя в цикл и многократно запрашивая события из приложения. Поскольку ваш код захватывает события напрямую, вместо того, чтобы позволить приложению отправлять эти события в обычном режиме, активные таймеры не смогут срабатывать до тех пор, пока ваша подпрограмма отслеживания мыши не завершится и не вернет управление приложению.
Это происходит МНОГО РАЗ, а мы даже не замечаем этого. Я имею в виду, что мы установили таймер на 10: 10: 10: 00, но цикл выполнения выполняет событие, которое длится до 10: 10: 10: 05, поэтому таймер запускается 10: 10: 10: 06
Точно так же, если таймер срабатывает, когда цикл выполнения находится в середине выполнения подпрограммы обработчика, таймер ожидает до следующего раза цикла выполнения, чтобы вызвать свою подпрограмму обработчика. Если цикл выполнения вообще не запущен, таймер никогда не срабатывает.
Вы можете настроить таймеры для генерации событий только один раз или несколько раз. Повторяющийся таймер автоматически меняет расписание на основе запланированного времени зажигания, а не фактического времени зажигания. Например, если таймер запланирован для срабатывания в определенное время и каждые 5 секунд после этого, запланированное время срабатывания всегда будет соответствовать исходным 5-секундным интервалам, даже если фактическое время срабатывания будет отложено. Если время срабатывания задерживается настолько, что пропускается одно или несколько запланированных времен срабатывания, таймер срабатывает только один раз на пропущенный период времени. После срабатывания за пропущенный период таймер переносится на следующее запланированное время срабатывания.
Вы не можете. ОС просто меняется для вас. например, когда пользователь нажимает, режим переключается на 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]
неявно его создаст.
Вкратце, режимы - это в основном механизм фильтрации для входов и таймеров.