Программирование SwiftOptionals и система типовРазработчик приложений для iOS

Практическая ситуация: функция получает экземпляр класса и переназначает свой параметр на новый объект. Изм...

Практическая ситуация: функция получает экземпляр класса и переназначает свой параметр на новый объект. Изменится ли переменная вызывающего кода?

Проходите собеседования с ИИ помощником Hintsage

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

Нет, переменная вызывающего кода не изменится: параметр функции получает копию ссылочного значения. После переназначения параметр указывает на новый объект только внутри функции, тогда как изменение состояния исходного объекта было бы видно вызывающему коду.

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

В Swift одновременно существуют value semantics для структур и перечислений и reference semantics для классов. Такое разделение позволяет выбирать между независимыми значениями и совместно используемым состоянием, не делая все типы объектами со скрытым общим доступом.

Для классов Swift использует ссылочную семантику, но это не означает передачу переменной-ссылки по умолчанию. Передаётся значение ссылки, поэтому копируется возможность обратиться к объекту, а не сам объект.

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

Неверно считать, что параметр класса является постоянным псевдонимом переменной вызывающего кода. Если внутри функции параметр переназначить, это изменит локальное связывание параметра, но не связывание исходной переменной.

При этом изменение свойств объекта через параметр обычно наблюдается снаружи, потому что обе переменные содержат ссылки на один экземпляр. Путаница между переназначением ссылки и изменением объекта приводит к ошибкам в API, особенно в функциях, которые должны заменить объект целиком.

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

Swift передаёт обычные параметры по значению. Для класса этим значением является ссылка на экземпляр, поэтому после передачи существуют две копии ссылки, указывающие на один объект.

Переназначение параметра меняет только локальную копию ссылки. Изменение свойства через параметр меняет общий объект и потому доступно через исходную переменную.

final class Box { var value: Int init(_ value: Int) { self.value = value } } func replace(_ box: Box) { box.value = 2 // Локальный параметр теперь указывает на другой объект. // Это не меняет переменную вызывающего кода. let box = Box(99) } let original = Box(1) replace(original) print(original.value) // 2

Если требуется заменить саму ссылку у вызывающего кода, используют возвращаемое значение или параметр inout. inout предоставляет функции доступ к хранилищу переменной вызывающего кода, поэтому переназначение такого параметра может изменить это хранилище; при этом действуют правила эксклюзивного доступа к памяти.

Важно отличать это от копирования объекта. Класс не копируется автоматически при передаче, поэтому изменение его изменяемого состояния видно всем владельцам ссылки. Для независимой копии нужен явно определённый механизм клонирования или другой способ создания нового экземпляра.

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

Представим функцию обновления текущей конфигурации приложения. Если она получает объект конфигурации и внутри создаёт новый объект, простое переназначение параметра не обновит конфигурацию, сохранённую у вызывающего кода.

Возможны три решения:

  • изменить свойства существующего объекта — просто, но сохраняет общий изменяемый объект и его побочные эффекты;
  • вернуть новый объект и присвоить его снаружи — явно показывает замену и обычно лучше контролируется;
  • принять ссылку через inout — позволяет менять исходную переменную внутри функции, но усиливает связанность и требует соблюдения правил эксклюзивного доступа.

Для конфигурации обычно выбирают возврат нового объекта: функция становится проще для тестирования, а факт замены явно виден в месте вызова. inout оправдан, когда операция действительно должна менять переданное хранилище, а не только вычислять результат.

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

  1. Вопрос: Можно ли утверждать, что при передаче класса в функцию объект всегда передаётся по ссылке?

    Ответ: Точнее говорить, что параметр передаётся по значению, а значением является ссылка на объект. Это различие важно: копируется ссылка, но не экземпляр, и переназначение параметра не меняет переменную вызывающего кода.

  2. Вопрос: Что изменится, если параметр объявлен как inout?

    Ответ: Функция получает доступ к хранилищу переменной вызывающего кода, а не только к копии ссылочного значения. Поэтому присваивание нового экземпляра параметру может заменить ссылку снаружи. Вызов требует явного указания намерения передать переменную для изменения, а Swift контролирует эксклюзивность такого доступа.

  3. Вопрос: Почему изменение свойства объекта через обычный параметр видно вызывающему коду, хотя переназначение параметра — нет?

    Ответ: В обоих местах изначально используется одна и та же ссылка на экземпляр. Изменение свойства обращается к общему объекту, поэтому результат виден через все ссылки. Переназначение меняет только локальное значение параметра и не затрагивает исходную переменную, хранящую отдельную копию ссылки.