В практической ситуации параметр функции помечен @autoclosure: в какой момент вычислится переданное выражение?
Переданное выражение не вычисляется до входа в функцию. Swift автоматически оборачивает его в замыкание, а само выражение вычисляется при вызове этого замыкания внутри функции.
Если функция не вызовет замыкание, выражение вообще не выполнится. Если замыкание вызвать несколько раз, выражение обычно вычислится столько же раз: @autoclosure не добавляет автоматическое кэширование результата.
Обычные параметры функций вычисляются до вызова функции. Это неудобно для API, где аргумент нужен только условно: например, сообщение об ошибке может требовать дорогого форматирования, хотя ошибки не произошло.
@autoclosure решает эту проблему, сохраняя компактный синтаксис вызова. Разработчику не нужно явно писать замыкание, но функция получает возможность отложить вычисление аргумента.
При обычном параметре побочные эффекты, вычисления и создание промежуточных объектов происходят ещё до того, как функция решит, нужен ли аргумент. Это может ухудшить производительность или привести к нежелательным изменениям состояния.
Скрытая отложенность, напротив, меняет момент выполнения выражения. Поэтому использование @autoclosure в API требует осторожности: вызов выглядит как передача готового значения, хотя фактически передаётся операция, которую функция может выполнить позже или не выполнить вовсе.
Компилятор преобразует выражение аргумента в замыкание типа () -> T. Функция получает это замыкание и сама определяет момент его вызова.
До вызова report функция makeMessage не выполняется. Внутри report выражение вычисляется в момент message(). Если убрать этот вызов, значение calls после report осталось бы равным нулю.
По умолчанию замыкание от @autoclosure является неescaping: функция не может сохранить его для использования после собственного возврата. Если отложенное вычисление должно произойти позже, параметр нужно явно объявить как @autoclosure @escaping.
Важно учитывать побочные эффекты и захваты. При сохранении escaping-замыкания захваченные значения и объекты подчиняются обычным правилам замыканий, включая возможное удержание экземпляров классов. Для чистых и дешёвых выражений отложенность обычно не даёт заметной пользы.
Представим API диагностики, которому нужно печатать подробное сообщение только при срабатывании условия. Формирование сообщения может включать сериализацию большого объекта, поэтому вычислять его заранее невыгодно.
Вариант с обычной строкой проще для понимания, но сообщение создаётся всегда. Явное замыкание даёт полный контроль и явно показывает отложенное вычисление, однако делает каждый вызов более многословным. @autoclosure сохраняет краткий вызов, но скрывает наличие замыкания и может ввести читателя в заблуждение относительно стоимости аргумента.
Для assertion-подобного API разумно выбрать @autoclosure, если контракт ясно документирует отложенное выполнение и параметр вызывается только при необходимости. Для общего бизнес-метода чаще предпочтительнее обычный параметр или явное замыкание, чтобы момент и стоимость вычисления были очевидны. Такой выбор уменьшает риск неожиданных побочных эффектов и упрощает сопровождение.
Может ли функция вызвать выражение @autoclosure несколько раз?
Да. Каждый вызов замыкания заново выполняет исходное выражение, если функция сама не сохранит результат в локальную переменную. Поэтому выражение с побочным эффектом может дать разные результаты при последовательных вызовах, а дорогое вычисление не следует вызывать многократно без необходимости.
Можно ли сохранить @autoclosure после возврата функции?
Не в обычном виде: по умолчанию это неescaping-параметр. Для сохранения нужно явно использовать сочетание @autoclosure @escaping, после чего функция может передать замыкание свойству или вернуть его. Это увеличивает время жизни захваченных объектов и может создать циклическую ссылку, поэтому нужно применять обычные правила управления захватами.
Чем @autoclosure отличается от явного параметра-замыкания?
Явное замыкание требует от вызывающего кода передать операцию явно, поэтому отложенность видна в месте вызова и проще контролируется. @autoclosure автоматически создаёт такое замыкание из выражения и делает интерфейс короче, но скрывает механизм.
Оба варианта не гарантируют однократное выполнение и не превращают результат в лениво кэшируемое значение. Выбор между ними — компромисс между удобством синтаксиса и прозрачностью поведения API.