Когда Swift вычисляет значение параметра по умолчанию при вызове функции?
Swift вычисляет значение параметра по умолчанию при каждом вызове, в котором соответствующий аргумент не передан. Вычисление происходит до входа в тело функции; если аргумент передан явно, выражение по умолчанию не вычисляется.
Параметры со значениями по умолчанию позволяют сделать API компактнее без создания отдельных перегруженных функций для каждого варианта вызова. Такой подход сохраняет одну реализацию поведения, но переносит вычисление значения по умолчанию в момент вызова.
Это особенно полезно для динамических значений: идентификаторов, текущего времени, настроек окружения или создаваемых объектов. Значение по умолчанию не является постоянным значением, заранее вычисленным при объявлении функции.
Ошибка возникает, когда разработчик воспринимает значение по умолчанию как одноразовую инициализацию. Тогда можно ошибочно ожидать, что несколько вызовов получат один и тот же результат, хотя выражение выполняется заново при каждом пропущенном аргументе.
Дополнительный риск связан с побочными эффектами. Если выражение увеличивает счётчик, создаёт объект или выполняет обращение к внешнему состоянию, эти действия произойдут только для вызовов без явного аргумента.
Значение по умолчанию логически подставляется в месте вызова функции. Сначала вычисляется выражение аргумента, затем полученный результат передаётся функции как обычный аргумент, после чего начинается выполнение тела функции.
В первом и третьем вызовах выполняется newID(). Во втором вызове значение передано явно, поэтому newID() не вызывается. В результате значение по умолчанию может зависеть от текущего состояния программы и не обязано быть одинаковым между вызовами.
Выражение по умолчанию вычисляется до входа в функцию, поэтому тело функции получает уже готовое значение. При этом оно не является частью типа функции: параметр всё равно остаётся параметром указанного типа, а информация о значении по умолчанию не входит в сигнатуру функционального значения.
Значения по умолчанию не следует использовать для скрытых тяжёлых операций или неожиданных побочных эффектов. Для сложной логики лучше явно передать зависимость, использовать фабрику или выполнить вычисление в отдельном слое, где его стоимость и жизненный цикл очевидны.
Сервис загрузки данных принимает идентификатор запроса, который обычно должен генерироваться автоматически, но иногда задаётся тестом или внешней системой. Вариант с перегруженными функциями создаёт несколько точек входа и может привести к расхождению поведения между ними.
Вариант с необязательным параметром позволяет принять nil, а затем вычислить идентификатор внутри тела функции. Его плюс — явный контроль момента вычисления; минус — функция получает дополнительное состояние и обязана отдельно обрабатывать отсутствие значения.
Параметр со значением по умолчанию обычно удобнее: обычный вызов краток, а тест может передать идентификатор явно. Однако генератор должен быть быстрым и предсказуемым, поскольку он запускается при каждом вызове без аргумента. В результате выбирается значение по умолчанию для простой генерации, а сложная подготовка выносится из аргумента в отдельную зависимость.
Нет. Если функция принимает целочисленный параметр со значением по умолчанию, её функциональный тип всё равно содержит обязательный параметр типа Int. Значение по умолчанию является частью синтаксиса вызова, а не частью типа функции.
Поэтому при присваивании функции переменной или передаче её как значения вызывающий контекст работает с обычной сигнатурой. Если вызов функционального значения требует аргумент, его нужно передать явно: механизм значения по умолчанию не добавляется к этому функциональному типу.
Побочный эффект не произойдёт. Swift вычисляет только выбранное выражение аргумента: либо явно переданное выражение, либо выражение значения по умолчанию, если аргумент пропущен.
Это важно для счётчиков, логирования, регистрации объектов и других операций, которые нельзя незаметно пропускать или выполнять лишний раз. Наличие значения по умолчанию не означает безусловное выполнение его выражения при каждом вызове.
Вызовы с пропущенным аргументом зависят от значения, известного вызывающему коду на этапе компиляции. Поэтому уже скомпилированный клиент может продолжить использовать прежнее значение до своей перекомпиляции.
Изменение значения по умолчанию следует рассматривать как изменение поведения API, а не только как внутреннюю правку реализации. Если новое значение должно немедленно применяться ко всем клиентам без их пересборки, вычисление лучше перенести в исполняемую реализацию функции или в другую централизованную точку.