Практическая ситуация: функция получает экземпляр класса и переназначает свой параметр на новый объект. Изменится ли переменная вызывающего кода?
Нет, переменная вызывающего кода не изменится: параметр функции получает копию ссылочного значения. После переназначения параметр указывает на новый объект только внутри функции, тогда как изменение состояния исходного объекта было бы видно вызывающему коду.
В Swift одновременно существуют value semantics для структур и перечислений и reference semantics для классов. Такое разделение позволяет выбирать между независимыми значениями и совместно используемым состоянием, не делая все типы объектами со скрытым общим доступом.
Для классов Swift использует ссылочную семантику, но это не означает передачу переменной-ссылки по умолчанию. Передаётся значение ссылки, поэтому копируется возможность обратиться к объекту, а не сам объект.
Неверно считать, что параметр класса является постоянным псевдонимом переменной вызывающего кода. Если внутри функции параметр переназначить, это изменит локальное связывание параметра, но не связывание исходной переменной.
При этом изменение свойств объекта через параметр обычно наблюдается снаружи, потому что обе переменные содержат ссылки на один экземпляр. Путаница между переназначением ссылки и изменением объекта приводит к ошибкам в API, особенно в функциях, которые должны заменить объект целиком.
Swift передаёт обычные параметры по значению. Для класса этим значением является ссылка на экземпляр, поэтому после передачи существуют две копии ссылки, указывающие на один объект.
Переназначение параметра меняет только локальную копию ссылки. Изменение свойства через параметр меняет общий объект и потому доступно через исходную переменную.
Если требуется заменить саму ссылку у вызывающего кода, используют возвращаемое значение или параметр inout. inout предоставляет функции доступ к хранилищу переменной вызывающего кода, поэтому переназначение такого параметра может изменить это хранилище; при этом действуют правила эксклюзивного доступа к памяти.
Важно отличать это от копирования объекта. Класс не копируется автоматически при передаче, поэтому изменение его изменяемого состояния видно всем владельцам ссылки. Для независимой копии нужен явно определённый механизм клонирования или другой способ создания нового экземпляра.
Представим функцию обновления текущей конфигурации приложения. Если она получает объект конфигурации и внутри создаёт новый объект, простое переназначение параметра не обновит конфигурацию, сохранённую у вызывающего кода.
Возможны три решения:
inout — позволяет менять исходную переменную внутри функции, но усиливает связанность и требует соблюдения правил эксклюзивного доступа.Для конфигурации обычно выбирают возврат нового объекта: функция становится проще для тестирования, а факт замены явно виден в месте вызова. inout оправдан, когда операция действительно должна менять переданное хранилище, а не только вычислять результат.
Вопрос: Можно ли утверждать, что при передаче класса в функцию объект всегда передаётся по ссылке?
Ответ: Точнее говорить, что параметр передаётся по значению, а значением является ссылка на объект. Это различие важно: копируется ссылка, но не экземпляр, и переназначение параметра не меняет переменную вызывающего кода.
Вопрос: Что изменится, если параметр объявлен как inout?
Ответ: Функция получает доступ к хранилищу переменной вызывающего кода, а не только к копии ссылочного значения. Поэтому присваивание нового экземпляра параметру может заменить ссылку снаружи. Вызов требует явного указания намерения передать переменную для изменения, а Swift контролирует эксклюзивность такого доступа.
Вопрос: Почему изменение свойства объекта через обычный параметр видно вызывающему коду, хотя переназначение параметра — нет?
Ответ: В обоих местах изначально используется одна и та же ссылка на экземпляр. Изменение свойства обращается к общему объекту, поэтому результат виден через все ссылки. Переназначение меняет только локальное значение параметра и не затрагивает исходную переменную, хранящую отдельную копию ссылки.