Почему класс может реализовать требующий изменения self метод протокола без пометки mutating?
Пометка mutating в требовании протокола означает, что реализация может изменить состояние экземпляра значения, включая при необходимости заменить весь self. Класс может реализовать такой метод без mutating, потому что экземпляр класса имеет ссылочную семантику: метод способен изменить объект через стабильную ссылку, не переназначая саму ссылку.
Swift объединяет типы-значения и ссылочные типы, но применяет к ним единый механизм протоколов. Поэтому протоколу нужно явно сообщить, что его метод допускает изменение self у структур или перечислений; для классов отдельная пометка не требуется.
Такой подход позволяет одному протоколу описывать операцию изменения состояния для разных категорий типов, не заставляя класс использовать семантику, предназначенную для значений.
Если метод протокола изменяет свойства структуры, вызов через переменную должен иметь право изменить сам экземпляр. Для этого реализация структуры обычно помечается mutating; без этого компилятор не разрешит изменение хранимых свойств.
У класса ситуация иная. Константа, содержащая ссылку на объект, запрещает заменить ссылку, но не запрещает изменить объект, если его свойства доступны для записи. Поэтому требование mutating не означает, что каждая реализация обязана иметь такую же пометку.
mutating относится не просто к изменению любого свойства, а к возможности изменить состояние самого значения self. Для структуры это может означать изменение одного свойства или полную замену экземпляра другим значением.
Класс реализует требующий mutating метод как обычный метод. Он может менять свои свойства, поскольку все копии ссылок указывают на один объект. При этом он не может произвольно заменить self новым экземпляром так, как это допускается для значения.
В примере Counter обязан использовать mutating, потому что меняет экземпляр структуры. Session реализует то же требование обычным методом: он меняет состояние объекта, на который указывает ссылка.
Это не означает, что класс всегда может менять любые данные. Ограничения доступа, let-свойства, private и другие правила Swift продолжают действовать. Кроме того, вызов метода через константу класса может менять изменяемые свойства объекта, тогда как вызов mutating-метода структуры через константу запрещён.
Допустим, протокол описывает сброс состояния компонента, а компонент может быть как структурой конфигурации, так и объектом сервиса. Если объявить требование без mutating, структура не сможет корректно реализовать метод, изменяющий собственные свойства. Если заставить все реализации быть mutating, класс не получит от этого дополнительной защиты и будет описан менее естественно.
Вариант с отдельными протоколами для структур и классов точнее разделяет семантику, но дублирует контракт и усложняет обобщённый код. Вариант с mutating в протоколе универсальнее: структуры явно подтверждают возможность изменения значения, а классы реализуют тот же контракт обычным методом.
Поэтому выбирают один протокол с mutating, когда операция логически означает изменение состояния независимо от категории типа. Результат — единый generic-код, принимающий и структуру, и класс, при сохранении корректных правил изменения для каждого типа.
Вопрос: Может ли структура реализовать mutating-требование протокола методом без mutating, если метод фактически ничего не меняет?
Ответ: Да, если реализация не изменяет self, она может быть объявлена без mutating. Требование протокола разрешает изменение, но не обязывает каждую реализацию им пользоваться. Однако если тело метода изменяет хранимое свойство или переназначает self, для структуры понадобится mutating.
Вопрос: Почему вызов mutating-метода структуры через константу запрещён даже при реализации, которая в конкретном случае ничего не меняет?
Ответ: Проверка выполняется по контракту метода, а не по текущему телу конкретной реализации. mutating сообщает, что вызов потенциально может изменить значение, поэтому переменная должна быть изменяемой. Это сохраняет единые гарантии для статического типа и не зависит от того, изменится ли состояние в данном запуске.
Вопрос: Что произойдёт при вызове такого протокольного метода через значение типа any Resettable?
Ответ: Для переменной-значения вызов должен происходить в изменяемом контексте, поскольку протокол допускает изменение self. Если контейнер объявлен как константа, вызов mutating-метода будет запрещён. Для экземпляра класса внутри existential-контейнера изменяются свойства самого объекта, но это не превращает ссылку в возможность заменить весь контейнер новым экземпляром.