UIScrollView призупиняє NSTimer, поки прокрутка не закінчиться


84

Поки UIScrollView(або його похідний клас) прокручується, здається, що всі NSTimersзапущені призупиняються, поки прокрутка не закінчиться.

Чи є спосіб обійти це? Нитки? Встановлення пріоритету? Що-небудь?



сім років потому ... stackoverflow.com/a/12625429/294884
Fattie

Відповіді:


201

Просте і просте у реалізації рішення - це зробити:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2
UITrackingRunLoopMode - це режим - і його загальнодоступний - ви можете використовувати для створення різних способів поведінки таймера.
Том Андерсен,

Полюбіть це, працює як шарм і з GLKViewController. Просто встановіть controller.pause на YES, коли з'явиться UIScrollView, і запустіть власний таймер, як описано. Змініть його, коли прокручуваний перегляд відхилено, і все! Мій цикл оновлення / візуалізації не дуже дорогий, тому це може допомогти.
Jeroen Bouma

3
Чудово! Чи повинен я видаляти таймер, коли я його анулюю чи ні? Дякую
Якопо Пенцо

Це працює для прокрутки, але зупиняє таймер, коли програма переходить у фоновий режим. Будь-яке рішення для досягнення обох?
Pulkit Sharma

23

Для тих, хто використовує Swift 3

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

7
Краще зателефонувати timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)як першу команду замість Timer.scheduleTimer(), оскільки scheduleTimer()додає таймер до циклу запуску, а наступний виклик - це ще одне додавання до того самого циклу запуску, але з іншим режимом. Не виконуйте двічі одну і ту ж роботу.
Accid Bright

8

Так, Пол має рацію, це проблема циклу запуску. Зокрема, вам потрібно скористатися методом NSRunLoop:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

7

Це швидка версія.

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

6

Вам потрібно запустити інший потік та інший цикл запуску, якщо ви хочете, щоб таймери спрацьовували під час прокрутки; оскільки таймери обробляються як частина циклу подій, якщо ви зайняті обробкою прокрутки вашого перегляду, ви ніколи не потрапите до таймерів. Хоча покарання за ефективність / заряд батареї, що працює на інших потоках, може не коштувати розгляду цієї справи.


11
Це не вимагає іншого потоку, див. Останню відповідь від Kashif. Я спробував, і він працює без додаткових потоків.
progrmr

3
Стрільба з гармат у горобців. Замість потоку, просто заплануйте таймер у правильному циклі запуску.
uliwitness

2
Я думаю, що відповідь Кашифа нижче - найкраща відповідь, оскільки для її вирішення потрібно лише додати 1 рядок коду.
Деймієн Мерфі.

3

для тих, хто використовує Swift 4:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)

1

tl; dr, runloop робить прокрутку, щоб не міг обробляти більше подій - якщо ви не встановите таймер вручну, щоб це також могло статися, коли runloop обробляє події дотику. Або спробуйте альтернативне рішення та скористайтеся GCD


Обов’язкове прочитання для будь-якого розробника iOS. Багато речей в кінцевому підсумку виконуються через RunLoop.

Походить від документів Apple .

Що таке Run Loop?

Цикл запуску дуже схожий на його назву. Це цикл, який ваш потік вводить і використовує для запуску обробників подій у відповідь на вхідні події

Як порушується доставка подій?

Оскільки таймери та інші періодичні події доставляються під час запуску циклу запуску, обхід цього циклу порушує доставку цих подій. Типовий приклад такої поведінки трапляється кожного разу, коли ви впроваджуєте програму відстеження миші, вводячи цикл і неодноразово вимагаючи від програми подій. Оскільки ваш код захоплює події безпосередньо, а не дозволяє додатку нормально відправляти ці події, активні таймери не зможуть спрацьовувати, поки ваша програма відстеження миші не вийде і не поверне керування додатку.

Що станеться, якщо таймер спрацьовує, коли цикл запуску знаходиться в середині виконання?

Це трапляється БАГАТО ЧАСІВ, а ми ніколи цього не помічаємо. Я маю на увазі, що ми вмикаємо таймер на 10: 10: 10: 00, але пробіг виконує подію, яка триває до 10: 10: 10: 05, отже, таймер спрацьовує 10: 10: 10: 06

Подібним чином, якщо таймер спрацьовує, коли цикл запуску знаходиться посередині виконання процедури обробника, таймер чекає наступного разу через цикл запуску, щоб викликати свою процедуру обробника. Якщо цикл запуску взагалі не працює, таймер ніколи не спрацьовує.

Чи буде прокрутка або щось, що заважає запуску, змінюватися весь час, коли мій таймер спрацює?

Ви можете налаштувати таймери для генерації подій лише один раз або кілька разів. Таймер, що повторюється, перепрограмує себе автоматично на основі запланованого часу стрільби, а не фактичного часу стрільби. Наприклад, якщо таймер планується спрацьовувати в певний час і кожні 5 секунд після цього, запланований час стрільби завжди припадає на початкові інтервали часу в 5 секунд, навіть якщо фактичний час стрільби затримується. Якщо час стрільби затримується настільки, що він пропускає один або кілька запланованих часів стрільби, таймер спрацьовує лише один раз за пропущений період часу. Після стрільби на пропущений період таймер переноситься на наступний запланований час стрільби.

Як я можу змінити режим RunLoops?

Ви не можете. ОС просто змінюється сама для вас. наприклад, коли користувач натискає, тоді режим перемикається на eventTracking. Після закінчення натискань користувача режим повертається до default. Якщо ви хочете, щоб щось було запущено в певному режимі, то це залежить від вас, щоб переконатися, що це станеться.


Рішення:

Коли користувач прокручує, режим Run Loop Mode стає 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]що неявно створить його.

У двох словах, режими в основному є механізмом фільтрації входів та таймерів

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.