Изображение взято со stackoverflow
Изображение взято со stackoverflow

Run Loop (цикл исполнения) является механизмом, который позволяет потокам обрабатывать события (events) бесконечно в любое время.

Run Loop представляет из себя объект, который управляет событиями и сообщениями, обрабатывает их, и предоставляет функцию точки входа для выполнения логики события.

После того, как поток выполнит эту функцию, он будет пребывать в цикле “прием сообщения -> ожидание -> обработка -> спячка до следующего сообщения -> прием сообщения” внутри функции до конца этого цикла (например, было передано сообщение завершения), после чего функция завершится возвратом.

В OSX/iOS у нас уже есть реализация этого механизма в виде NSRunLoop и CFRunLoopRef. CFRunLoopRef является частью фреймворка CoreFoundation. Он предоставляет API для функций на чистом C, которое является потокобезопасным.

NSRunLoop — это обертка, берущая за основу CFRunLoopRef, которая предоставляет объектно-ориентированное API, но это API не является потокобезопасным.

В системе по умолчанию предусмотрено пять режимов:

  1. kCFRunLoopDefaultMode: дефолтный для вашего приложения режим, обычно в этом режиме выполняется основной (main) поток.

  2. UITrackingRunLoopMode: режим отслеживания интерфейса, используемый ScrollView для отслеживания касаний и слайдов, чтобы гарантировать, что интерфейс не зависит от других режимов при слайдинге.

  3. UIInitializationRunLoopMode: первый режим, в который приложение входит при запуске, он не будет больше использоваться после завершения запуска.

  4. GSEventReceiveRunLoopMode: внутренний режим для приема системных событий, обычно не используется.

  5. kCFRunLoopCommonModes: это режим-заполнитель, не имеющий практического значения.

Четыре функции цикла исполнения:

  • Принимать вводимые пользователем данные, не прерывая выполнения программы.

  • Решать, когда события должны обрабатываться программой.

  • Разделять вызовы.

  • Экономить время процессора

Цикл исполнения основного потока запускается по умолчанию. В iOS-приложении после запуска функция main() будет следующей:

OBJ-C
int main (int argc, char * argv [])
  {
     @autoreleasepool {
       return UIApplicationMain(argc, 
                                argv, 
                                nil,   
                                NSStringFromClass ([appDelegate class]));
     }
}
SWIFT:
UIApplicationMain(CommandLine.argc, 
                  CommandLine.unsafeArgv, 
                  nil, 
                  NSStringFromClass(AppDelegateTest.self))

Ключевым моментом является функция UIApplicationMain(). Этот метод устанавливает объект NSRunLoop для основного потока (main thread).

Это объясняет, почему наше приложение может находиться в состоянии сна, когда мы с ним не взаимодействуем (никакие действия не выполняются) и в то же время немедленно реагировать, когда какое-либо действие наконец совершается.

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

Obj-c
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
Swift
let runLoop = RunLoop.current

Цикл исполнения (run loop) — это цикл обработки событий, который используется для непрерывного мониторинга и обработки входящих событий и назначения их соответствующим таргетам для дальнейшей обработки.

NSRunLoop — более умная модель обработки сообщений. Он умело абстрагировал и инкапсулировал процесс обработки сообщений, чтобы вам не приходилось иметь дело с очень тривиальной и низкоуровневой конкретикой обработки сообщений. Каждое сообщение упаковано в источник ввода (input source) или источник таймера (timer source). Использование цикла исполнения позволяет вашему потоку работать, когда есть работа, и переходить в спящий режим, когда работы нет, что может значительно сэкономить системные ресурсы.

 

Изображение взято со stackoverflow. Ссылка ниже.
Изображение взято со stackoverflow. Ссылка ниже.

Когда следует использовать цикл исполнения?

Cocoa предоставляет код для запуска основного цикла (main loop) программы и автоматического запуска цикла исполнения. Метод запуска UIApplication в программе IOS (или NSApplication в Mac OS X) используется как часть этапа запуска программы. Он запустит основной цикл программы, когда будет завершен ее запуск.

Для вспомогательных же потоков вам самим необходимо определить, нужны ли им циклы исполнения. Если нужны, то вам придется настроить и запустить их самостоятельно. По умолчанию вам не рекомендуется запускать цикл исполнения для потока ни при каких обстоятельствах. Например, когда вы используете потоки для обработки заранее определенной длительной задачи, вам следует избегать запуска цикла исполнения. Запуск цикла исполнения оправдан только если вы хотите активно взаимодействовать с потокам, например:

  • использовать кастомные порты или источники ввода для коммуникации с другими потоками

  • использовать потоковые таймеры (threaded timers)

  • использовать метод Cocoa с любым performSelector’ом

  • перевести поток в режим периодической работы

NSRunLoop класс в Cocoa  не потокобезопасен

Мы не можем управлять объектом цикла исполнения одного потока в другом потоке — это может привести к неожиданным результатам. К счастью, opaque класс CFRunLoopRef в CoreFundation является потокобезопасным, что позволяет миксовать между собой типы циклов исполнения. Класс NSRunLoop в Cocoa может быть реализован с помощью методов инстанса:

-(CFRunLoopRef) getCFRunLoop;

Остается только выберать соответствующий класс CFRunLoopRef для обеспечения потокобезопасности.

Управление циклом исполнения не является полностью автоматическим

Нам все еще нужно писать в потоке код, запускающий цикл исполнения в подходящее время, и должным образом реагировать на события, конечно, если цикл исполнения необходим в этом потоке. Более того, нам также нужно использовать операторы while/for, чтобы сделать цикл исполнения бесконечным. Следующий код успешно запускает цикл исполнения:

::OBJ-C::
BOOL isRunning = NO;
 do {
      isRunning = [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDatedistantFuture]];
 } while (isRunning);
::Swift::
var isRunning = false
do {
isRunning = RunLoop.current.run(mode: .default, before: .distantFuture)
} while (isRunning)

Цикл исполнения также отвечает за создание и выпуск autorelease пула.

Полезные ссылки:
Как компилятор узнает, что была нажата кнопка?
Threading Programming Guide


Материал подготовлен в рамках курса «iOS Developer. Professional». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

Комментарии (0)