openlang

unowned self в повторяющемся Timer просто откладывает падение

Заменил [weak self] на [unowned self] в блоке повторяющегося Timer, чтобы избавиться от опционала, и закрыл экран. Следующий тик упал.

Ничего загадочного. Run loop держит запланированный таймер, а таймер держит свой блок. Значит, замыкание живёт дольше контроллера, пока кто-нибудь не вызовет invalidate(). unowned сообщает Swift, что контроллер переживёт замыкание, а здесь всё наоборот, и в книге прямо сказано, что обращение к unowned-ссылке после освобождения объекта даёт ошибку времени выполнения.

Сильный захват не падает. Он хуже, только тише. Контроллер не может исчезнуть, пока таймер держит блок, поэтому invalidate() в deinit так и не выполнится, а таймер будет тикать бесконечно.

Работает слабая ссылка, и решение всё равно остаётся. Должен ли этот колбэк продлевать жизнь владельца? Нет. Что тогда значит тик без владельца? Здесь ничего, таймер должен остановиться, поэтому deinit вызывает invalidate(), а guard let self else { return } остаётся дешёвой проверкой.

Разговор продолжается 2

Следуя доводам
  1. Ссылка на ответ

    С тиками слабый захват справляется. deinit контроллера выполняется, и invalidate() в нём останавливает таймер, так что после закрытия экрана тиков больше нет.

    А вот поток пока никто не проверял. deinit выполняется там, где отпущена последняя сильная ссылка, а invalidate() положено вызывать на потоке, чей run loop держит таймер. Если контроллер отпускается только на главном потоке, всё работает, но это удача обстановки, а не обещание кода. Беда проявится только тогда, когда последнюю ссылку отпустит кто-то на другом потоке.

  2. Ссылка на ответ

    Стоит отметить, что то же самое случается, когда таймер превращается в цикл Task вокруг Task.sleep, и там это легче проглядеть. У неструктурированной задачи нет родителя, так что при уходе контроллера её никто не отменит. Если замыкание держит self сильно, цикл держит контроллер, и deinit даже не получает шанса её отменить. [weak self] помогает, только если сильная ссылка берётся внутри каждой итерации. guard let self над циклом держит её весь цикл. В Kotlin корутина, запущенная в области, отменяется вместе с этой областью, и именно этого мне не хватает на стороне Swift. Есть ли что-то, что привязывает неструктурированный Task к времени жизни объекта, кроме как хранить handle и вызывать cancel() в deinit?

Другие стороны того же вопроса

  1. Предупреждение про висячий указатель пропало, а указатель остался

    Передаю имя в библиотеку на C++ через обёртку, и сначала написал всё в одну строку, CString::new(name).unwrap().as_ptr(), и r…

    Ответы 1rustcpp
  2. getline оставляет буфер вызывающему, даже когда возвращает -1

    POSIX getline выделяет или увеличивает буфер через переданный указатель, и этот буфер принадлежит вызывающему с первого же вы…

    Ответы 4c
  3. Ссылка на интерфейс и ссылка на объект для одного экземпляра

    Ошибка времени жизни, которую я чаще всего встречаю в старом коде на Delphi, возникает, когда на одном экземпляре смешивают д…

    Ответы 5object-pascal