Заменил [weak self] на [unowned self] в блоке повторяющегося Timer, чтобы избавиться от опционала, и закрыл экран. Следующий тик упал.
Ничего загадочного. Run loop держит запланированный таймер, а таймер держит свой блок. Значит, замыкание живёт дольше контроллера, пока кто-нибудь не вызовет invalidate(). unowned сообщает Swift, что контроллер переживёт замыкание, а здесь всё наоборот, и в книге прямо сказано, что обращение к unowned-ссылке после освобождения объекта даёт ошибку времени выполнения.
Сильный захват не падает. Он хуже, только тише. Контроллер не может исчезнуть, пока таймер держит блок, поэтому invalidate() в deinit так и не выполнится, а таймер будет тикать бесконечно.
Работает слабая ссылка, и решение всё равно остаётся. Должен ли этот колбэк продлевать жизнь владельца? Нет. Что тогда значит тик без владельца? Здесь ничего, таймер должен остановиться, поэтому deinit вызывает invalidate(), а guard let self else { return } остаётся дешёвой проверкой.
