Runloop swift что это такое
Русские Блоги
RunLoop в iOS
RunLoop в iOS
Что такое RunLoop
Буквально это бегущий цикл. Наша общая программа состоит в том, чтобы выполнить поток, который является прямой линией. Есть начальная и конечная точки. Runloop всегда рисует круг на потоке, пробегает круг и постоянно обнаруживает некоторые круги. События щелчков, таймеры и т. Д. Запускают выполнение после обнаружения, снова переводят в спящий режим после выполнения, а затем обнаруживают во время режима сна, если он не отключен, он всегда будет работать, в противном случае он продолжит цикл. Внутренняя структура представляет собой цикл do-while, в котором непрерывно обрабатываются различные задачи (такие как таймер, источник и Observer).
Основная роль RunLoop
RunLoop и потоки
Как видно из приведенного выше кода, существует взаимно-однозначное соответствие между потоками и RunLoop, и отношение сохраняется в глобальном словаре. Когда поток только что был создан, RunLoop не было, если вы не извлекаете его активно, он никогда не будет там. Создание RunLoop происходит при первом получении, а уничтожение RunLoop происходит в конце потока. Вы можете получить его RunLoop только внутри потока (кроме основного потока).
Примечание: инициализация NSRunLoop не требует alloc, нужно только вызвать [NSRunLoop currentRunLoop] в этом потоке,
RunLoop и связанные классы
(В основном для 5 классов RunLoop в Core Foundation)
Класс CFRunLoopModeRef не предоставляется извне, но инкапсулируется через интерфейс CFRunLoopRef. Их отношения таковы:
CFRunLoopModeRef
CFRunLoopModeRef представляет режим запуска RunLoop. Именно потому, что у runloop есть источники, таймеры и наблюдатели, runloop всегда может работать. Без них runloop будет напрямую выходить из цикла.
RunLoop содержит несколько режимов, и каждый режим содержит несколько источников / таймеров / наблюдателей
При каждом запуске RunLoop может быть указан только один из них. Этот режим называется CurrentMode
Если вам нужно переключить режим, вы можете только выйти из цикла и указать новый режим для входа в цикл.
Каждый режим может установить свой собственный источник / таймер / наблюдатель, чтобы он не влиял друг на друга
5 режимов зарегистрированы по умолчанию (Apple открыла только первые два)
kCFRunLoopDefaultMode: режим приложения по умолчанию, обычно основной поток работает в этом режиме.
UITrackingRunLoopMode: режим отслеживания интерфейса, используемый для ScrollerView для отслеживания касания и скольжения, чтобы гарантировать, что другие интерфейсы не будут влиять на интерфейс при скольжении.
UIInitalizaationRunLoopMode: первый режим, введенный при запуске приложения, больше не будет использоваться после запуска
GSEventReceiveRunLoopMode: принимает внутренний режим (отрисовка чертежа и т. Д.) Системных событий, обычно не используемых
kCFRunLoopCommonModes: это режим заполнителя, а не реальный режим
CFRunLoopTimerRef
CFRunLoopSourceRef
CFRunLoopObserverRef
Если вы добавите Observer в runloop, вы можете использовать только функции CF, обычно используемые для перехвата системных событий и т. Д.
Логика обработки RunLoop
Что касается памяти runloop, в runloop runloop будет соответствовать потоку, а пул автоматического выпуска предназначен для некоторых объектов текущего потока, он будет освобожден один раз перед kCFRunLoopBeforeWaiting и в следующий раз Он будет воссоздан при запуске,
Варианты использования RunLoop в разработке
1, запустить резидентный поток (чтобы дочерний поток не входил в мертвое состояние, ожидал сообщений от других потоков, обрабатывал события)
2, может заставить определенные события выполняться в определенном режиме
Если изображение, загруженное из Интернета, отображается в UIScrollView или UITableView, пользовательский интерфейс иногда зависает при скольжении экрана. Вы можете использовать runloop для решения этой проблемы.
Русские Блоги
001-глубокое понимание iOS RunLoop
Пожалуйста, опишите RunLoop в разработке для iOS в одном предложении.
RunLoop является «do <> while;», ответственным за отправку «live» каждому потоку.
оглавление
Что такое RunLoop
Структура RunLoop
Реализация RunLoop
Когда используется RunLoop
Пример приложения RunLoop
Что такое RunLoop
В общем, блоки генерации запускаются сверху вниз, когда они выполняются (разве функция или метод не вызывают друг друга? Ха-ха), после запуска поток заканчивается и завершается. Давайте сначала рассмотрим случай, когда в процессе приложения присутствует только основной поток. Затем возникает проблема, мы записываем код функции в основной поток, пользователь запускает приложение, код основного потока завершается, поток завершается, процесс завершается, а затем программа завершается. То есть программа не будет ждать операции пользователя (щелчок, слайд и т. Д.), Мы можем подумать о такой функции, как scanf, но такая функция также является однократной, пользователь ожидает ввода данных пользователем. После того, как пользовательский ввод завершен? Эта строка кода исчезает, и последующий код выполняется и выходит как обычно, не ожидая операции пользователя в течение длительного времени.
Умный, вы могли подумать о способе : Не нормально ли писать цикл как для или как? Каждый раз, когда пользовательское событие обрабатывается, оно возвращается обратно, ожидая следующей интерактивной операции клиента.При получении пользовательского события операции оно обрабатывается в цикле, а затем переходит в следующий цикл, ожидая пользовательской операции.
Это самая основная функция, которую необходимо реализовать в RunLoop. RunLoop должен сделать следующее:
1. Обрабатывать и распространять события / сообщения
2. Пусть поток что-то делает, ничего не делает, экономит ресурсы
3. Пусть поток не выходит сразу (остановка в цикле)
Структура RunLoop
Сначала перейдите к картине, заимствуйте Bireme картину (спасибо)
RunLoop может содержать несколько режимов, и каждый режим может содержать несколько источников, наблюдателей и таймеров.
Определение RunLoop
Давайте поговорим о _commonModes в RunLoop, мы можем использовать официально предоставленные
Давайте добавим режим в коллекцию _commonModes, чтобы при каждом изменении содержимого RunLoop RunLoop автоматически синхронизировал источник, таймер и наблюдатель в _commonModeItems со всеми режимами в _commonModes.
Это также объясняет, что таймеры, подобные каруселям изображений, лучше всего добавлять в NSRunLoopCommonModes вместо NSDefaultRunLoopMode. Поскольку, если вы добавляете таймер к NSDefaultRunLoopMode, то есть только этот NSDefaultRunLoopMode содержит этот таймер, при нормальных обстоятельствах mainLoop запускается в NSDefaultRunLoopMode, и когда мы перемещаем экран, mainLoop переключается на выполнение в UIEventTrackingRunLoopMode, режим не включает карусель изображения Таймер, поэтому таймер не отслеживается при скольжении, и карусель изображения будет приостановлена (фактически, она также остановится при редактировании textField, по той же причине), пока mainLoop снова не переключится в режим, содержащий таймер. Если таймер добавлен в NSRunLoopCommonModes, внутренним эквивалентом является добавление таймера в коллекцию _commonModeItems. MainLoop будет распределять все элементы в коллекции (добавить или связать, официальный документ использует связанный). Каждый режим в коллекции _commonModes Среди них UIEventTrackingRunLoopMode является одним из них.
Соответствующие функции в CoreFoundation:
«Source0 содержит только обратный вызов (указатель на функцию), он не может активно инициировать событие. При использовании необходимо вызвать CFRunLoopSourceSignal (source), пометить этот Source как ожидающий, а затем вручную вызвать CFRunLoopWakeUp (runloop), чтобы разбудить RunLoop, Пусть он справится с этим событием. «
source1 основан на портах Mach, используемых для мониторинга событий и сообщений, полученных портами Mach нашего приложения. Он может активно запускать события и обратные вызовы, а также пробуждать цикл выполнения.
Что касается источников, официальные документы делят источники на две категории: источники на основе портов и источники нестандартного ввода.
Input sources deliver events asynchronously to your threads. The
source of the event depends on the type of the input source, which is generally one of two categories. Port-based input sources monitor your
application’s Mach ports. Custom input sources monitor custom sources of events.As far as your run loop is concerned,it should not matter whether an input source is port-based or custom. The system typically implements input sources of both types that you can use as is.
The only difference between the two sources is how they are signaled. Port-based sources are signaled automatically by the kernel, and custom sources must be signaled manually from another thread.
Единственное различие между двумя входными источниками состоит в том, что источник на основе порта автоматически уведомляется ядром (нам не нужно отправлять сообщения вручную), в то время как пользовательский источник должен вручную отправлять сообщения (уведомления) в другие потоки.
Во-первых, ядро завершает связь, а во-вторых, мы отправляем его вручную.
Когда runloop работает в текущем режиме, _observers в режиме будет уведомлено соответствующим образом. Например, когда вы сразу вводите runloop, когда вы хотите обработать источник, таймер и блок, когда вы собираетесь спать и когда вы выходите из runloop. Это уведомление о некоторых действиях и циклах операторов.
_timers опущен, приведенный выше простой пример был объяснен.
Реализация RunLoop
Официальная документация написана так:
Each time you run it, your thread’s run
Цикл обрабатывает ожидающие события и генерирует уведомления для любого
attached observers. The order in which it does this is very specific and is as follows:
1. Сообщите наблюдателям, что цикл запуска введен.
2. Сообщите наблюдателям, что все готовые таймеры вот-вот сработают. (Уведомите наблюдателей, чтобы они работали с точными таймерами).
3. Уведомить наблюдателей о том, что любые источники ввода, которые не основаны на портах, собираются сработать (уведомить наблюдателей, чтобы иметь дело с источниками ввода не на основе портов)
4. Запустите любые не входящие в порт источники ввода, которые готовы к стрельбе.
5. Если источник ввода на основе порта готов и ожидает запуска, немедленно обработайте событие. Перейдите к шагу 9. (Если существует источник ввода на основе порта, который нужно обработать, немедленно обработайте событие и перейдите к шагу 9 )
6. Уведомить наблюдателей, что поток собирается спать (уведомить наблюдателей, что поток собирается спать).
7. Переведите поток в спящий режим до тех пор, пока не произойдет одно из следующих событий: (Пусть поток спит, пока не произойдет одно из следующих событий 🙂
(1) Событие приходит для источника ввода на основе порта. (Событие приходит для источника ввода на основе порта.)
(2) Таймер срабатывает. (Активация таймера)
(3) Срок ожидания, установленный для цикла выполнения, истекает. (Значение времени ожидания, установленное для цикла выполнения, истекло, то есть время ожидания)
(4) Run цикл явно проснулся (runloop явно проснулся)
8.Notify observers that the thread just
проснулся. (Сообщите наблюдателям, что поток только что проснулся)
9. Обработайте ожидающее событие. (Обработайте ожидающее событие)
(1)If a user-defined timer fired, process the timer event and
перезапустите цикл. Перейдите к шагу 2. (Если обрабатывается пользовательский таймер, перезапустите цикл выполнения и вернитесь к шагу 2)
(2) Если сработал источник входного сигнала, доставьте событие. (Если источник входного сигнала активирован, отправьте событие)
(3) Если цикл выполнения был явно разбужен, но время его еще не истекло, перезапустите цикл. Перейдите к шагу 2. (Если цикл запуска проснулся и время не истекло, перезапустите цикл и вернитесь к шагу 2).
10.Notify observers that the run loop
завершился (уведомить наблюдателя о завершении runloop)
Поскольку уведомления наблюдателя для таймера и входных источников доставляются до того, как эти события действительно произойдут, между временем уведомлений и временем фактических событий может быть разрыв. Если время между этими событиями является критическим, вы можете использовать режим сна и уведомления о пробуждении от сна, чтобы помочь вам соотнести (связать) время между фактическими событиями.
Поскольку время уведомления раньше, чем время, когда событие действительно произошло (обработано), в середине есть небольшой разрыв. Если требование времени между событиями очень строго, то вы можете использовать режим сна и бодрствования от сна, чтобы уведомить их о связанном времени.
Следует добавить, что слева от шага 7 на рисунке причина пробуждения потока все еще одна, то есть установленное время ожидания ограничено.
Исходный код для запуска цикла выполнения в CoreFoundation выглядит следующим образом:
Базовая реализация RunLoop
Как видно из приведенного выше кода, ядро RunLoop основано на порте mach, а функция, вызываемая при переходе в спящий режим, является mach_msg (). Чтобы объяснить эту логику, системная архитектура OSX / iOS кратко представлена ниже.
Apple официально делит всю систему на четыре вышеуказанных уровня:
Давайте подробнее рассмотрим основную архитектуру Дарвина:
Сообщение Маха на самом деле представляет собой двоичный пакет данных (BLOB). Заголовок определяет текущий порт local_port и целевой порт remote_port. Отправка и получение сообщений осуществляются через один и тот же API. Его опция отмечает направление доставки сообщения:
Чтобы отправлять и получать сообщения, функция mach_msg () фактически вызывает ловушку Маха (trap), а именно функцию mach_msg_trap (). Концепция trap эквивалентна системному вызову в Mach. Когда вы вызываете mach_msg_trap () в пользовательском режиме, механизм ловушек будет запущен для переключения в режим ядра, а функция mach_msg (), реализованная ядром в режиме ядра, завершит фактическую работу, как показано ниже:
Ядром RunLoop является mach_msg () (см. Шаг 7 приведенного выше кода). RunLoop вызывает эту функцию для получения сообщений. Если никто больше не отправляет сообщение порта, ядро переводит поток в состояние ожидания. Например, если вы запустите приложение iOS в симуляторе, а затем нажмете «Пауза», когда приложение не будет работать, вы увидите, что стек вызовов основного потока остается на mach_msg_trap ().
Когда используется RunLoop
Зная, что такое Run Loop, после внутренней реализации эта проблема становится очень простой. Официальная документация гласит:
Use ports or custom input sources to communicate with other threads.
1. Используйте порты или пользовательский источник ввода для связи с другими потоками;
Use timers on the thread.
2. Используется таймер
Use any of the performSelector… methods in a Cocoa application.
3. Метод executeSelector используется в приложении cocoaapp.
Keep the thread around to perform periodic tasks.
4. При разрешении потоку выполнять периодические задачи
Для подпотоков, когда вам нужно использовать runloop, не забудьте начать, иначе добавление дополнительных селекторов и таймеров не является мягким.
RunLoop Swift: что это такое, для чего нужен?
В системе АйОС имеется так называемая многослойность, информации о которой достаточно мало, вследствие чего многим пользователям приходится обращаться к различным источникам для получения полноценной информации.
Определенные программы должны быть установлены в обязательном порядке для полноценного использования определенных функциональных возможностей устройства. Одной из них и является RunLoop. Что это и с какой целью применяется на устройстве?
Все, что необходимо знать о работе с данной утилитой
Несмотря на то, что RunLoop не работает с полноценными потоками, а используется только для улучшения и упрощения выполнения определенных операций, стоит сказать о том, что такая утилита является необходимой, вследствие чего стоит рассмотреть ее работу и особенности.
RunLoop представляет собой определенного рода бесконечный цикл, который используется на том или ином устройстве для обработки и проведения координации всех событий, которые поступают на установленный вход.
В том случае, если поток идет нескончаемым образом, приложение может влиять на скорость обработки прочих запросов, однако такое происходит достаточно редко. К тому же, у каждого отдельного потока имеется свой определенный RunLoop, который является ассоциативным.
Для главного потока запуск происходит в автоматическом режиме, а в том случае, если создаются дополнительные потоки, для них открываются новые направления обработки для того, чтобы процесс осуществлялся в наиболее короткие сроки.
Так, например, все системные события запускаются и в обязательном порядке детально обрабатываются в главном потоке, где происходит их переформирование, можно осуществлять управление и вносить определенные корректировки для получения необходимого результата.
По сути, программа предполагает процесс отличия обычной мобильной программы от интерактивного приложения, которое требует больше возможностей от устройства и имеет большое количество требований к работе системы.
Она работает в постоянном режиме и способствует повышению качества работы различных файлов.
Русские Блоги
Полное руководство по iOS RunLoop
Что такое RunLoop
концепция
Я считаю, что большинству людей знаком этот отрывок, и они примерно знают, что такое RunLoop.
Если вы продолжите спрашивать: как RunLoop реализует механизм сна? Какие задачи может обрабатывать RunLoop и как они решаются? Какие приложения есть у RunLoop в системе iOS?
Многие люди могут быть в растерянности. Это распространенный метод, используемый компаниями при отборе интервьюеров для проверки глубины понимания вопроса интервьюером. Это отражает способность человека к обучению и будущую пластичность со стороны. Вот почему многие крупные компании предпочитают задавать фундаментальные вопросы.
Конечно, также очень полезно понимать базовую реализацию, оптимизировать ежедневное написание кода и активно избегать некоторых ямок.
Теперь давайте вместе узнаем больше о RunLoop
Говоря из основной функции
Сначала посмотрите на следующую программу командной строки:
Если мы запустим программу, мы обнаружим, что после того, как командная строка выведет «Hello, World!», Процесс программы автоматически завершится:
Причина проста, потому что наш основной поток завершает логику кода, возвращает 0, и программа завершается.
Давайте посмотрим на запись основной программы (main.m) приложения iOS, которое мы обычно пишем:
Код очень похож на версию основной функции для командной строки.В основном, возвращаемое значение функции UIApplicationMain возвращается напрямую.
Но странно то, что наше приложение для iOS не завершается как программа командной строки. Почему?
Фактически, UIKit запустит основной цикл выполнения в функции UIApplicationMain, так что основной цикл выполнения будет продолжать работать (спать, когда нечего делать), так что функция UIApplicationMain не вернется немедленно, и наше приложение не выйдет
Если вы нажмете системную точку останова CFRunLoopRunInMode, вы увидите, что в функции UIApplicationMain система запускает основной цикл выполнения:
Структура RunLoop
В CF структура, относящаяся к RunLoop, имеет следующие категории:
Структура RunLoop следующая:
Возможно, вам придется время от времени оглядываться на это изображение, чтобы глубже понять структуру RunLoop и взаимодействие между ними.
Когда мы устанавливаем точку останова в нашем собственном приложении, существует вероятность 90%, что мы увидим одну из следующих шести функций в стеке вызовов, все из которых являются функциями обратного вызова в разных контекстах RunLoop:
Это также показывает, что может делать наш RunLoop, и большинство функций системы связаны с RunLoop. GCD не основан на RunLoop (кроме отправки в основную очередь), поэтому вы не можете увидеть их в стеке вызовов GCD.
Структура данных, соответствующая RunLoop:
Преобразование между CFRunLoopRef и NSRunLoop осуществляется бесплатно. Что касается конкретного кода реализации RunLoop, мы упомянем его ниже.
RunLoop предоставляет следующие функции (CF ** в скобках обозначает имя соответствующей структуры данных в библиотеке CF):
Когда RunLoop запущен, он войдет в цикл do while, как показано ниже:
Когда RunLoop не обрабатывает задачи, он переходит в спящий режим. Если вы нажмете «Пауза» в XCode, вы увидите, что стек вызовов основного потока остается в mach_msg_trap ().
соответствует исходному коду CF, RunLoop зависнет
В функции mach_msg поток попадет в mach_msg_trap.
Что касается спящего режима RunLoop, здесь задействован ключевой момент, а именно связь порта Mach.
Ядром RunLoop является mach_msg () (шаг 7 на рисунке). RunLoop вызывает эту функцию для получения сообщений. Если больше никто не отправляет сообщение порта, ядро переводит поток в состояние ожидания. Например, если вы запустите приложение iOS в симуляторе, а затем нажмете «Пауза», когда приложение неподвижно, вы увидите, что стек вызовов основного потока остается в mach_msg_trap ().
Чтобы объяснить эту логику, давайте кратко представим системную архитектуру OSX / iOS.
Архитектура Дарвина выглядит следующим образом:
Среди них три компонента на аппаратном уровне: Mach, BSD, IOKit (включая некоторый контент, не отмеченный выше), вместе образуют ядро XNU.
Внутренний цикл ядра XNU называется Mach. Как микроядро, оно предоставляет лишь очень небольшое количество базовых услуг, таких как планирование процессора и IPC (межпроцессное взаимодействие).
Уровень BSD можно рассматривать как внешнее кольцо вокруг уровня Маха, который обеспечивает такие функции, как управление процессами, файловая система и сеть.
Уровень IOKit предоставляет объектно-ориентированную (C ++) структуру для драйверов устройств.
В Mach все реализуется через объекты. Процессы, потоки и виртуальная память называются «объектами».
Хорошо, вернемся к RunLoop.
Thread & RunLoop
Хотя связь между RunLoop и Thread очень близка, не каждый поток имеет RunLoop.
В iOS, помимо того, что система автоматически создает цикл выполнения для основного потока, в дочернем потоке нам необходимо вручную получить цикл выполнения, соответствующий потоку:
Название метода очень сбивает с толку. Метод Get может заставить нас подумать, что уже существует RunLoop, соответствующий потоку. Фактически, это метод отложенной загрузки. Реализация кода:
Функция Get RunLoop сначала обнаружит, существует ли RunLoop, соответствующий текущему потоку, в статическом __main runLoop или TSD (данные, зависящие от потока) текущего дочернего потока. Если он не найден, тогда
Эти две функции в конечном итоге вызовут метод _CFRunLoopGet0 (pthread_t t), параметром является сам текущий поток, упрощенная версия:
Другой момент заключается в том, что сам RunLoop не запускается. Нам нужно вручную вызвать метод Run (Main RunLoop будет запущен системой), прежде чем наш RunLoop запустится. Статический (обратите внимание, что статический здесь не означает бездействующий) RunLoop ничего не сделает
RunLoop Mode
Из вышесказанного мы можем знать, что RunLoop будет обрабатывать определенные транзакции только в случае Run.
Итак, когда дело доходит до Run, режим RunLoop здесь неотделим.
Каждый раз, когда RunLoop запускает Run, необходимо указать режим, называемый RunLoop Mode. Mode определяет задачи, которые RunLoop может обрабатывать в этом Run. Для задач, которые не принадлежат текущему режиму, вам необходимо переключить RunLoop в соответствующий режим, а затем снова вызвать метод выполнения для обработки.
Это можно увидеть в коде CF, функция запуска RunLoop в CF выглядит следующим образом:
Взаимосвязь между RunLoop, RunLoop mode и RunLoop элементами показана на следующем рисунке:
RunLoop может иметь несколько режимов, и каждый режим имеет 4 агрегатные структуры, соответствующие событиям source0, source1 и timer, которые может обрабатывать текущий режим, и наблюдателям состояния RunLoop, зарегистрированным в текущем режиме.
Код режима RunLoop следующий:
Мы можем настроить режим, и система также предопределила для нас некоторые режимы:
Здесь нам нужно позаботиться только о режиме по умолчанию и режиме отслеживания событий, а также о специальном «режиме»: Общие режимы.
Default mode Это режим RunLoop по умолчанию. При создании RunLoop соответственно будет создан режим по умолчанию. Остальные режимы лениво загружаются.
Event tracking mode Это режим, используемый Какао при работе с интенсивными входящими событиями (такими как скольжение прокрутки).
Common modes По сути, это не режим, а набор режимов. В программе Какао по умолчанию включены режимы по умолчанию, модальный режим и режим отслеживания событий. В программе Core Foundation по умолчанию есть только режим по умолчанию. Мы также можем добавить пользовательские режимы к общим режимам. Если мы хотим, чтобы событие обрабатывалось в нескольких режимах, мы можем напрямую зарегистрировать событие в общих режимах.
Здесь у нас есть небольшое понимание того, как режим добавляется к общим режимам с точки зрения исходного кода.
Давайте вернемся к структуре режима в определении CFRunLoop:
Общий режим может «помечать» себя как общий.Особый метод заключается в том, что система добавит имя режима в _commonModes.
Когда элемент добавляется в общие режимы, сначала в _commonModeItems цикла выполнения будет добавлена запись, а затем будут пройдены все _commonModes, и элемент будет добавлен в режим, который был помечен как общий.
Когда режим в целом добавляется к обычным режимам, будет выполнена другая операция. Система вызовет void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) метод. Внутри метода система сначала установит общий режим (добавьте имя режима в _commonModes). Затем все элементы в _commonModeItems будут добавлены в этот режим. Обратите внимание, что элемент в режиме здесь не будет добавлен в _commonModeItems.
Это означает, что когда мы добавляем режим в целом к общим режимам, режим может реагировать на события элемента общего режима, а его собственные элементы режима не будут включены в другие режимы, отмеченные как общие. ответ.
Когда мы передаем задачу в RunLoop, нам нужно указать, в каком режиме она будет обрабатываться. Если не указано иное, по умолчанию она будет обрабатываться в режиме по умолчанию. 。
Задача может быть отправлена в несколько режимов. Если одна и та же задача отправляется в режим несколько раз, в этом режиме будет сохранена только одна задача, потому что в коде будут похожие суждения:
Например, таймер реализован на основе RunLoop.При создании таймера мы можем указать режим таймера:
Если мы не укажем режим RunLoop для таймера, он будет добавлен к режиму по умолчанию для запуска по умолчанию.
Это также объясняет, почему, когда мы перемещаем полосу прокрутки, событие таймера не вызывается. Потому что, если мы добавим таймер в режим по умолчанию основного потока по умолчанию, когда пользователь перемещает полосу прокрутки, основной цикл выполнения переключится в режим отслеживания событий для приема скользящих событий, требующих интенсивной обработки. Не сработает.
Решение состоит в том, чтобы добавить таймер в общие режимы, чтобы его можно было вызывать как в режиме по умолчанию, так и в режиме отслеживания событий.
RunLoop Source
В документах Apple события, которые RunLoop может обрабатывать, делятся на источники ввода и события таймера. Изображение ниже взято с официального сайта Apple. Не обращайте внимания на детали, которые сбивают с толку. Просто посмотрите на взаимосвязь между потоками, источниками ввода и источниками таймера. Не обращайте внимания на содержимое.
О событиях таймера мы поговорим ниже, а теперь давайте посмотрим на источник ввода.
Согласно исходному коду CF, входной источник подразделяется на две категории: source0 и source1 в RunLoop. И source0, и source1 имеют структуру __CFRunLoopSource, что означает:
Source0 и source1 различаются кодом в сочетании с _context.
source0 VS source1
Source1 управляется RunLoop, а ядро - портом Mach.
Source0 смещен в сторону уровня приложения. Например, обработка UIEvent в Cocoa будет отправлена в основной RunLoop в форме source0.
Timer
Сколько таймеров мы часто используем?
CADisplayLink (реализация обратного вызова путем доставки source1 в RunLoop)
Тип сделки в NSObject выполняет ряд функций на самом деле является событием Timer, что может быть не так очевидно:
Базовая реализация этой функции задержки выполнения такая же, как и NSTimer, согласно официальной документации Apple:
NSTimer & PerformSelector:afterDelay:
Структура NSTimer в исходном коде CF такая:
Что касается тайминга таймера, то это достигается через время обработки ядра или время GCD.
В RunLoop, когда NSTimer активирован, спящий RunLoop будет пробужден через _timerPort (если это NSTimer, реализованный через GCD, он будет через другой выделенный порт MAC очереди CGD), а затем RunLoop вызовет
Для обратного вызова функции огня таймера.
Ниже приведен стек вызовов функций при запуске NSTimer:
PerformSelector: afterDelay: имеет похожий стек вызовов, потому что базовая реализация такая же, как NSTimer.
Observer
Структура Observer в CF следующая:
Роль Observer состоит в том, чтобы позволить извне отслеживать состояние выполнения RunLoop, чтобы выполнять некоторые операции в соответствии с разным временем.
Когда приложение запускается, система зарегистрирует двух наблюдателей в основном цикле выполнения, и оба их обратных вызова будут _wrapRunLoopWithAutoreleasePoolHandler ().
Код, выполняемый в основном потоке, обычно записывается в обратных вызовах событий и обратных вызовах таймера. Эти обратные вызовы будут созданы RunLoop AutoreleasePool.
окружает его, поэтому утечек памяти не будет, и разработчикам не нужно отображать создание пула.
События, которые может отслеживать Observer, представлены поразрядным XOR в CF:
Вот,
kCFRunLoopEntry,
kCFRunLoopExit
вызывается только один раз в каждом цикле RunLoop, чтобы указать, что он собирается войти в цикл и выйти из цикла.
kCFRunLoopBeforeTimers,
kCFRunLoopBeforeSources,
kCFRunLoopBeforeWaiting,
kCFRunLoopAfterWaiting
Эти уведомления будут отправляться внутри цикла и могут вызываться несколько раз.
Анализ исходного кода RunLoop
Исходный код RunLoop CF относительно длинный, и он также вводит некоторую логику Windows для кроссплатформенности. Я удалил здесь некоторую несущественную логику и добавил комментарии, чтобы вы могли сравнить и понять реальный код.
в CFRunLoopRunSpecific В функции ядро - вызвать __CFRunLoopRun Пусть RunLoop действительно работает:
Когда в нашем приложении нет обработки событий, с помощью кнопки паузы приложения XCode вы можете увидеть стек основного цикла RunLoop в состоянии сна:
Функции, реализованные Apple с помощью RunLoop
Ответ на инцидент
Ответ на событие устройств iOS включает RunLoop.
Упомяните реакцию устройств iOS на инциденты, я думаю, у всех будет общее понимание:
Здесь возникают две проблемы: шаги с (3) по (5) обрабатываются в процессе, а шаги с (1) по (2) связаны с обменом данными между аппаратным обеспечением устройства, операционной системой iOS и целевым приложением. Какие общие шаги?
До того, как наше приложение получит запрос на событие, основной цикл выполнения находится в неактивном состоянии mach_msg_trap. Тогда кто его разбудит?
Ладно, не волнуйтесь, давайте проанализируем медленно. Во-первых, мы используем команду po для распечатки основного цикла выполнения приложения (обратите внимание, что конкретное имя функции, соответствующее обратному вызову, может отображаться только в симуляторе, что может быть связано с контролем доступа на реальной машине). Многие вещи можно понять медленно, посмотрев на них, и нетерпеливые ученики также могут игнорировать это. Достаточно взглянуть на следующий анализ:
Мы обнаружим, что система добавит связку источника и наблюдателя к основному RunLoop.
Здесь нам нужно только обратить внимание на:
Источник события source0, зарегистрированный в kCFRunLoopDefaultMode, UITrackingRunLoopMode и kCFRunLoopCommonModes:
Как ни умно, да, дочерний поток действительно существует. Мы приостанавливаем работу приложения и увидим, что в дополнение к основному потоку система также автоматически создает для нас несколько дочерних потоков, один из которых называется
Из имени потока видно, что это поток, созданный UIKit для приема событий (далее именуемый потоком выборки событий).
Мы распечатываем цикл выполнения com.apple.uikit.eventfetch-thread. По сравнению с основным RunLoop, он намного проще, но все же на первый взгляд впечатляет. Нетерпеливые студенты также могут сразу перейти к выводу:
Здесь нам нужно только знать, что поток выборки событий имеет только один режим по умолчанию (не считая общих режимов), а source1 зарегистрирован как в режиме по умолчанию, так и в обычных режимах:
Его обратный вызов
Есть дверь.Поскольку это тип source1, система может разбудить RunLoop потока выборки событий через порт mach для выполнения обратного вызова __IOHIDEventSystemClientQueueCallback.
Мы нажимаем символические точки останова __IOHIDEventSystemClientQueueCallback, __handleEventQueue, а затем нажимаем кнопку, чтобы протестировать процесс выполнения. Можно обнаружить, что __IOHIDEventSystemClientQueueCallback и __handleEventQueue вызываются по очереди для обработки событий.
Конкретный процесс можно найти здесьiphonedevwiki
После тестирования делается вывод:
Когда пользователь запускает событие, IOKit.framework генерирует событие IOHIDEvent и принимается SpringBoard. SpringBoard будет использовать порт mach для генерации source1, чтобы разбудить RunLoop целевого приложения com.apple.uikit.eventfetch-thread. Поток Eventfetch установит source0, соответствующий __handleEventQueue в основном цикле выполнения, в состояние signalaled == Yes и одновременно пробудит основной цикл RunLoop. mainRunLoop вызывает __handleEventQueue для обработки очереди событий.
Распознавание жестов
Распознавание жестов iOS также зависит от RunLoop.
Когда система распознает жест, она прерывает обратный вызов серии касаний и обновляет состояние UIGestureRecognizer соответствующего жеста (требуется обновление?)
UIKit зарегистрирует наблюдателя в основном RunLoop (фактически, они зарегистрированы в режиме отслеживания, режиме по умолчанию и обычном режиме соответственно):
Проверьте здесь атрибут активности 0x20 в сочетании с определением битового флага в CF:
Видно, что наблюдатель отслеживает событие kCFRunLoopBeforeWaiting основного цикла RunLoop. Каждый раз, когда основной цикл RunLoop собирается засыпать, запускается наблюдатель и одновременно вызывается функция обратного вызова _UIGestureRecognizerUpdateObserver. _UIGestureRecognizerUpdateObserver обнаружит распознаватель (создать, запустить, уничтожить), который в настоящее время необходимо обновить.
Если срабатывает жест, в обратном вызове _UIGestureRecognizerUpdateObserver будет использоваться внутренний класс UIKit UIGestureEnvironment Провести серию обработки. Событие жеста будет доставлено в очередь событий приложения. Поток обработки этого события жеста должен быть аналогичен описанной выше обработке события. __HandleEventQueueInternal будет вызываться внутренне для обработки события жеста и передачи внутреннего класса UIKit. UIGestureEnvironment Для обработки этого события жеста и, наконец, обратного вызова для обратного вызова жеста, который мы написали.
Обновление интерфейса
Когда нам нужно обновить интерфейс, например, UIView / CALayer, вызывающий setNeedsLayout / setNeedsDisplay, или обновление кадра UIView или уровня пользовательского интерфейса.
Фактически, система не сразу начинает обновлять интерфейс, а сначала отправляет запрос обновления пользовательского интерфейса, а затем ожидает следующего основного цикла RunLoop, централизованной обработки (преимущество централизованной обработки заключается в том, что вы можете объединить некоторые Повторяющееся или противоречивое обновление пользовательского интерфейса). Эта реализация достигается путем отслеживания уведомлений перед ожиданием и выходом основного цикла выполнения.
В наблюдателе основного цикла RunLoop мы можем видеть наблюдателя следующих атрибутов (также зарегистрированных в режимах по умолчанию, отслеживание, общие режимы),
Обратный вызов наблюдателя
Он вызовет внутренний
В этой функции отправляются все запросы на обновление интерфейса, интерфейс обновляется и вызываются соответствующие обратные вызовы:
NSTimer
Соответствующая структура NSTimer в слое CF выглядит следующим образом, и преобразование между ними осуществляется бесплатно:
Общий поток такого события таймера от регистрации до выполнения показан на рисунке:
Если вы хотите узнать больше о базовой реализации таймера, вы можете обратиться сюда:Изучите принцип реализации NSTimer из исходного кода RunLoop
Стек функций при срабатывании таймера:
PerformSelector:afterDealy
Базовая реализация серии функций PerformSelector: afterDealy фактически добавляет таймер в режим RunLoop целевого потока по умолчанию, поэтому эту функцию нельзя использовать без потока, запущенного RunLoop.
PerformSelectorOnThread(mainThread):
Другие функции от PerformSelector до указанного потока также реализуются на основе RunLoop. Когда мы вызываем PerformSelectorOnThread (mainThread), система добавит source0 в режим по умолчанию целевого потока, а в обратном вызове source0 будет выполнен наш селектор.
Следовательно, если целевой поток не запускает RunLoop, серия функций PerformSelectorOnThread не может вступить в силу.
dispatch to main queue
Когда вызывается dispatch_async (dispatch_get_main_queue (), block), libDispatch отправит сообщение в RunLoop основного потока, RunLoop будет пробужден, получит этот блок из сообщения и обратится к нему.CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() Выполнить этот блок. Но эта логика ограничена отправкой в основной поток, а отправка в другие потоки по-прежнему обрабатывается libDispatch.
Когда мы устанавливаем точку останова в главном блоке диспетчерской очереди, мы можем увидеть следующий стек вызовов:
Обратите внимание, что это только для системы блоков, отправленных в основную очередь.
Сетевой запрос
NSURLConnection
До iOS 7 мы использовали NSURLConnection для передачи данных по сети. Базовый сетевой обратный вызов NSURLConnection основан на RunLoop потока, инициировавшего сетевой запрос. 。
То есть, если сетевой запрос инициирован в дочернем потоке, если RunLoop дочернего потока не выполняется или дочерний поток завершается до того, как будет возвращен результат сетевого запроса, NSURLConnection не вступит в силу.
Чтобы узнать о конкретных отношениях между NSURLConnection и RunLoop, вы можете обратиться сюда:
Глубокое понимание RunLoop
NSURLSession
В конце концов, NSURLConnection уже является антиквариатом, и его больше не рекомендуется использовать (проверив стек вызовов NSURLConnection, вы обнаружите, что сетевой кеш также будет использовать sqlite для его хранения, и он также должен полагаться на RunLoop, который действительно низкий).
В версиях после iOS 7 мы должны использовать NSURLSession для сетевых запросов. Реализация NSURLSession основана на GCD и полностью отделена от RunLoop.
Реализация NSURLSession на основе GCD означает, что мы можем инициировать сетевые запросы в любом потоке, независимо от того, находится ли RunLoop в режиме Run, и не заботимся о том, завершится ли поток инициирующего запроса до возврата сетевого запроса.
Пример приложения RunLoop
Когда нам понадобится RunLoop? Apple перечислила нам следующие сценарии:
Поток сохранить
Мы можем использовать следующий код, чтобы дочерний поток ждал порта mach, чтобы RunLoop не выходил из цикла:
Причина передачи на аутсорсинг уровня пула автоматического выпуска в коде заключается в том, что система не создает автоматически пул автоматического выпуска для дочерних потоков. Чтобы предотвратить утечки памяти, мы должны создать пул автоматического выпуска в начале всех дочерних потоков.
Здесь мы помещаем порт mach для текущего цикла выполнения, чтобы цикл выполнения всегда был пустым. Следовательно, пока мы удаляем порт из цикла выполнения, цикл выполнения никогда не остановится. (Если только он не истечет или не остановлен другим потоком или процессом).
Для выхода из цикла выполнения можно вызвать следующий код:
Здесь возникает проблема, а именно: как выйти из цикла выполнения. Есть два способа:
Оба эти метода возможны. Runloop выполнит некоторую очистку перед выходом. В то же время он отправит уведомление наблюдателю.
Хотя мы также можем выйти из цикла выполнения, удалив все источники ввода и таймеры, этот метод ненадежен. Потому что иногда система может регистрировать некоторые входные источники или таймеры, которые находятся вне нашего контроля.
AsyncDisplayKit
AsyncDisplayKit Это фреймворк, введенный Facebook для обеспечения бесперебойной работы интерфейса.
Основная идея состоит в том, чтобы поместить задачи интерфейса, которые могут быть обработаны фоновым потоком, и попытаться выполнить их в фоновом режиме. Только когда он наконец отобразится, он вернется в основной поток.
Здесь используются концепции, связанные с RunLoop. Однако, поскольку автор не понимает основы, заинтересованные студенты могут найти информацию самостоятельно.
Безопасность потоков RunLoop
На уровне CF CFRunLoopRef является потокобезопасным.
На уровне Какао NSRunLoop не является потокобезопасным.Мы должны гарантировать, что любые операции runloop выполняются в потоке, соответствующем NSRunLoop, в противном случае будут возникать непредсказуемые результаты.
Общие вопросы на собеседовании, связанные с RunLoop
подводить итоги
Хорошо, здесь у нас есть более полное представление о RunLoop.
На самом деле RunLoop не таинственный, пока вы понимаете его принцип, все уладится.
Что касается RunLoop, основное внимание уделяется пониманию четырех концепций механизма сна / пробуждения на основе порта Mach, режима Runloop, источника ввода, таймера и наблюдателя, а также того, как iOS использует эти вещи для достижения Системная функция.
Ссылка
Посоветуйте еще одну вики по небу:
iPhoneDevWiki