Как создать и запустить собственный Run Loop в iOS/macOS?

«Как создать и запустить собственный Run Loop в iOS/macOS?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, можно создать собственный Run Loop с помощью низкоуровневого API Core Foundation — CFRunLoopCreate(). Однако в подавляющем большинстве сценариев используется существующий Run Loop текущего потока (RunLoop.current).

Создание нового Run Loop (редкий случай):

import Foundation

// Создание нового CFRunLoopRef
let customRunLoop = CFRunLoopCreate()

// Добавление источника событий (например, таймера или порта)
let source = ... // CFRunLoopSourceRef
CFRunLoopAddSource(customRunLoop, source, CFRunLoopMode.defaultMode)

// Запуск Run Loop (блокирует текущий поток!)
CFRunLoopRun()

Работа с Run Loop текущего потока (типичный use case):

// Получение Run Loop текущего потока
let currentRunLoop = RunLoop.current

// Добавление источника (например, таймера)
let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
    print("Tick")
}
currentRunLoop.add(timer, forMode: .default)

// Запуск Run Loop (блокирует поток до остановки)
currentRunLoop.run()
// ИЛИ управляемый запуск на время:
currentRunLoop.run(until: Date().addingTimeInterval(5.0))

Важные замечания:

  • Источники: Run Loop немедленно завершится, если в нем нет хотя бы одного источника (timer, port, source).
  • Главный поток: Главный поток приложения уже имеет работающий Run Loop, управляемый UIApplication.
  • Фоновые потоки: Создание/запуск Run Loop актуально для вторичных потоков, которым необходимо обрабатывать асинхронные события (например, сетевые сокеты в старом стиле).
  • Блокировка: Метод run() блокирует поток. Для фоновых задач предпочтительнее использовать run(mode:before:) или run(until:) в цикле для более контролируемого выполнения.