После попытки сделать делегат слабой ссылкой компилятор отклоняет объявление. Какое ограничение типа отсутс...

После попытки сделать делегат слабой ссылкой компилятор отклоняет объявление. Какое ограничение типа отсутствует у EventHandler?

protocol EventHandler {
    func handle()
}

final class Controller {
    weak var handler: EventHandler?

    func run() {
        handler?.handle()
    }
}
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

EventHandler должен быть ограничен только ссылочными типами: protocol EventHandler: AnyObject. После этого weak var handler: EventHandler? станет допустимым.

weak применим к ссылкам на экземпляры классов, потому что только такие экземпляры управляются ARC. Протокол без AnyObject может быть реализован структурой или перечислением, для которых слабая ссылка не имеет смысла.

Исторический контекст

Слабые ссылки решают проблему владения объектами в модели ручного или автоматического подсчёта ссылок: они позволяют обращаться к объекту, не увеличивая его счётчик сильных ссылок. Когда последний владелец исчезает, ARC уничтожает объект, а обычная weak-ссылка обнуляется.

Значимые для ARC сущности в Swift — это экземпляры классов. Значения структур и перечислений копируются по правилам value semantics и не имеют независимого времени жизни, которым можно было бы управлять через слабую ссылку.

Постановка проблемы

Протокол EventHandler в исходном виде описывает только набор методов, но не ограничивает тип реализации классом. Поэтому его может реализовать структура:

struct LoggingHandler: EventHandler { func handle() { print("handled") } }

Для такого значения нельзя создать weak-ссылку. Если разрешить подобное объявление без ограничения, семантика свойства была бы неоднозначной: ARC не управляет временем жизни значений так, как он управляет временем жизни экземпляров классов.

Подробное решение

Нужно сделать протокол class-bound:

protocol EventHandler: AnyObject { func handle() } final class Controller { weak var handler: EventHandler? }

AnyObject означает, что протокол могут реализовывать только классы. Теперь значение handler является слабой ссылкой на объект: оно не удерживает обработчик живым и автоматически становится nil, когда обработчик уничтожен.

Опциональность нужна потому, что слабая ссылка может не указывать ни на какой объект. Кроме того, объект обработчика может исчезнуть раньше Controller, после чего обращение через handler безопасно даст nil.

Если обработчик обязан жить не меньше контроллера, вместо weak можно использовать сильное свойство. Это проще, но может создать цикл владения, если обработчик одновременно хранит контроллер. Ограничение AnyObject само по себе цикл не предотвращает — оно только делает слабую ссылку типобезопасной и применимой.

Ситуация из практики

В приложении контроллер хранит делегат сетевого слоя. Рассматривались два варианта: объявить протокол без ограничения и хранить делегат сильно либо добавить AnyObject и использовать weak.

Сильное свойство не требует class-bound-протокола, но может удерживать делегат, а через обратную ссылку — и контроллер. Использование weak предотвращает владение делегатом, однако исключает структуры из числа допустимых реализаций.

Для делегата, который по смыслу является долгоживущим объектом и может ссылаться обратно на контроллер, выбран вариант protocol EventHandler: AnyObject и weak var handler. В результате контроллер не удерживает делегат после удаления его владельца, а обращение к уже уничтоженному делегату безопасно возвращает nil.

Что кандидаты часто упускают

  1. Можно ли заменить AnyObject на class в объявлении протокола?

В старом синтаксисе Swift использовалась запись protocol EventHandler: class. Современный и рекомендуемый вариант — AnyObject; он явно выражает ограничение протокола ссылочными типами. Смысл ограничения тот же: реализация должна быть экземпляром класса.

  1. Достаточно ли class-bound-протокола, чтобы избежать утечки памяти?

Нет. Например, контроллер может сильно владеть обработчиком, а обработчик — сильно владеть контроллером. Оба объекта останутся достижимыми через цикл, даже если приложение больше не использует их. AnyObject разрешает weak, но разработчик всё равно должен выбрать направление владения и при необходимости ослабить одну из ссылок.

  1. Что изменится, если объявить handler сильным?

Объект обработчика будет удерживаться свойством handler, пока контроллер не будет уничтожен или свойство не получит другое значение. Это может быть правильным решением, если контроллер действительно владеет обработчиком. Но для делегата сильная ссылка часто ошибочна: при обратной сильной ссылке возникает цикл, который ARC автоматически не разорвёт.