Управление памятью в Swift
ГЛАВА I
IOS Run Loop: Что? Когда? Зачем?
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: это режим-заполнитель, не имеющий практического значения.
Четыре функции цикла исполнения:
1
Принимать вводимые пользователем данные, не прерывая выполнения программы
2
Решать, когда события должны обрабатываться программой
3
Разделять вызовы
4
Экономить время процессора
Ключевым моментом является функция UIApplicationMain(). Этот метод устанавливает объект NSRunLoop для основного потока (main thread)

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

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

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

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

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

  • использовать кастомные порты или источники ввода для коммуникации с другими потоками
  • использовать потоковые таймеры (threaded timers)
  • использовать метод Cocoa с любым performSelector'ом
  • перевести поток в режим периодической работы
Timer. Working with runloops
One common problem folks hit when using timers is that they won't fire when the user is interacting with your app. For example, if the user has their finger touching the screen so they can scroll through a table view, your regular timers won't get fired.

This happens because we're implicitly creating our timer on the defaultRunLoopMode, which is effectively the main thread of our application. This will then get paused while the user is actively interacting with our UI, then reactivated when they stop.

The easiest solution is to create the timer without scheduling it directly, then add it by hand to a runloop of your choosing. In this case, .common is the one we want: it allows our timers to fire even when the UI is being used.
let context = ["user": "@twostraws"]
let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: context, repeats: true)
RunLoop.current.add(timer, forMode: .common)
Synchronizing your timer with screen updates
Some people, particularly those making games, try to use timers to have some work done before every frame is drawn – i.e., 60 or 120 frames per second, depending on your device.

This is a mistake: timers are not designed for that level of accuracy, and you have no way of knowing how much time has elapsed since the last frame was drawn. So, you might think you have 1/60th or 1/120th of a second to run your code, but in practice half of that might already have passed before your timer was triggered.

So, if you want to have some code run immediately after the previous display update, you should use CADisplayLink instead. I've written some example code for that already (see How to synchronize code to drawing using CADisplayLink), but here's a quick snippet:

link
let displayLink = CADisplayLink(target: self, selector: #selector(fireTimer))
displayLink.add(to: .current, forMode: .default)