Swift 5.8. Что нового?
Хотя в Swift вносят очень много важных и мажорных изменений, о которых уже можно узнать из Swift Evolution, далеко не все эти изменения войдут в текущий релиз. Этот выпуск Swift скорее предназначен для оптимизации и улучшения функциональности, что должно облегчить процесс перехода на Swift 5.8 и сделать его более гладким, особенно после всех тех значительных изменений, которые были внесены в Swift 5.7.
Из этой статьи вы узнаете о самых важных изменениях в новом релизе с примерами кода и пояснениями. Для работы понадобиться версия Xcode 14.3 или выше. Так же стоит учесть, что некоторые изменения требуют особого флага компилятора, который придется использовать до выхода языка Swift 6.
Снятие ограничений на переменные в теле замыкающих выражений
SE-0373 ослабляет некоторые ограничения на переменные, используемые в теле замыкающих выражений, позволяя писать код, который ранее возвращал ошибку компиляции.
Например, мы можем использовать ленивые переменные непосредственно в конструкторах результатов, как показано ниже:
Данный пример раскрывает идею, но не несет никакой практической пользы, т.к. вместо ленивой переменный мы можем использовать константу и это никак не повлияет на конечный результат. Чтобы раскрыть весь потенциал этой концепции, нужен более проработанный пример:
Здесь мы определяем действия, которые нужно выполнить в зависимости от статуса пользователя:
- Если бы мы не использовали ленивую переменную, то метод
fetchUsername()
вызывался бы перманентно вне зависимости от текущего статуса пользователя. - Если вообще отказаться от переменной, как таковой, то нам пришлось бы вызывать метод
fetchUserName()
непосредственно в первых двух кейсах, а это дублирование одного и того же кода. - Если переменную
user
определить как вычисляемое свойство, то оно будет вызвано во второй раз, когда пользователь нажмет кнопку «Subscribe now».
Таким образом, благодаря возможности использовать ленивые свойства непосредственно в теле свойства body
, код получился наиболее оптимальным.
Это нововведение также позволяет использовать в теле замыкающих выражение обёртки над свойствами и локальные вычисляемые свойства:
Хотя практической пользы от такого кода я не вижу, т.к. увеличение счетчика tapCount
никак не будет влиять на обновление интерфейса. Да изменения будут сохраняться в UserDefaults
, но само по себе использование обертки @AppStorage
не приведет к повторному вызову свойства body
.
Обратное развёртывание функции
SE-0376 добавляет новый атрибут @backDeployed
, который позволяет использовать новые API в более старых версиях фреймворков. Благодаря этому атрибуту код функции записывается в двоичный файл приложения и уже в рантайме в зависимости от версии iOS используется либо заранее подготовленный файл, либо нативная реализация нового API.
При этом надо понимать, что @backDeployed
применяется только к функциям, методам, сабскриптам и вычисляемым свойствам. Это значит, что его можно использовать для небольших изменений в API, например, таких, как модификатор fontDesign()
, который был представлен в iOS 16.1. В то же время @backDeployed
не получится использовать для кода, требующего использования новых типов данных, например как модификатор scrollBounceBehavior()
, который зависит от нового типа ScrollBounceBehavior
.
Давайте рассмотрим это нововведение на примере представленного в iOS 16.4 модификатора для отображения моноширного текста monospaced(_ isActive:)
:
Если бы изменение программного кода было реализовано таким образом, то Swift использовал бы версию библиотеки SwiftUI, которая уже есть в системе, если такая версия существует. Если же такой версии нет, то Swift использовал бы старую версию библиотеки для обеспечения совместимости с более ранними версиями iOS (как минимум с версией 14.0).
Однако на практике это не так просто. Хотя изменение кода не создает новых видов данных, и кажется, что оно может легко поддерживаться более старыми версиями iOS, мы не знаем, какие виды данных использует SwiftUI внутри своего кода. Поэтому мы не можем точно предсказать, что можно и что нельзя обновлять для старых версий iOS.
Неявное использование self для слабых ссылок после их развертывания
SE-0365 упрощает работу со слабыми ссылками внутри блоков замыкания, позволяя обращаться к ним, опуская ключевое слово self, что делает код проще:
До версии Swift 5.8 этот код будет выдавать ошибку компиляции, т.к. Xcode будет ожидать ключевого слова self
перед обращением к свойству класса fireCount
.
Лаконичные идентификаторы для имен файлов
Изменения, предложенные в SE-0274, касаются идентификатора #file
. Теперь он будет содержать только название модуля и имени файла, например, MyApp/ContentView.swift. Раньше #file
содержал полный путь к файлу, который был очень длинным и мог содержать чувствительную информацию. Эти изменения сделают код более читаемым и уменьшат количество информации, которую нужно скрывать.
Важно: В данный момент это поведение не включено по умолчанию. Но разработчики могут выбрать использование этой функции заранее, добавив специальный флаг компилятора -enable-upcoming-feature ConciseMagicFile
в настройках проекта Other Swift Flags в Xcode.
Если вы хотите сохранить старое поведение после включения этого флага, то вместо #file
следует использовать #filePath
:
В Swift Evolution proposal for this change упоминается о больших улучшениях в размерах бинарных файлов и скорости выполнения программ. Также в этом предложении есть интересный абзац, в котором объясняется, почему использование полного пути к файлу не всегда хорошая идея. Например, в полном пути к файлу может содержаться имя пользователя, подсказки о конфигурации сборочной фермы или даже имя, которое вы дали внешнему диску в честь Sailor Scout:
“The full path to a source file may contain a developer’s username, hints about the configuration of a build farm, proprietary versions or identifiers, or the Sailor Scout you named an external disk after.”
Открытие экзистенциальных аргументов для опциональных параметров
SE-0375 исправляет небольшую, но раздражающую проблему, связанную с использованием универсальных (дженерик) функций с опциональными значениями. Ранее, в версии Swift 5.7, использование протоколов для вызова универсальных функций не работало с опциональными значениями, но теперь в Swift 5.8 это исправлено:
В Swift 5.7 этот код выдаёт довольно непонятное сообщение об ошибке “Type ‘any Numeric’ cannot conform to ‘Numeric’”, поэтому приятно видеть, что это несоответствие устранено.
Поддержка downcast’ов коллекций в шаблонах cast’ов
Нововведение устраняет еще одну небольшую, но потенциально раздражающую несогласованность в Swift, где downcast коллекции — например, приведение массива ClassA
к массиву другого типа, который наследуется от ClassA
- не разрешалось в некоторых обстоятельствах.
Например, этот код теперь допустим в Swift 5.8, тогда как ранее он не работал:
До Swift 5.8 это привело бы к сообщению об ошибке: “Collection downcast in cast pattern is not implemented; use an explicit downcast to ‘[Dog]’ instead.”. На практике, синтаксис типа if let dogs = pets as? [Dog] {}
работал прекрасно, поэтому скорее всего эта ошибка встречалась редко. Тем не менее текущее нововведение устраняет еще одно несоответствие в языке, что всегда приветствуется.
И это еще не все!
Есть два дополнительных изменения, которые стоит упомянуть кратко.
SE-0368 вводит новый тип StaticBigInt
, который должен упростить добавление новых, более крупных типов целых чисел в будущем.
Второе изменение SE-0372 касается функций сортировки в Swift. Теперь они официально отмечены как “устойчивые”. Это означает, что элементы, которые равны по значению, будут оставаться в том же порядке, в котором были в исходном массиве после сортировки.
Кроме того в Swift 5.8 появятся еще две новые фичи, которые пока не доступны в бета-версиях Xcode:
- SE-0369: Add CustomDebugStringConvertible conformance to AnyKeyPath добавляет соответствие протоколу
CustomDebugStringConvertible
дляAnyKeyPath
. Это позволит отображать отладочную ссылку как\ParentTypeName.PropertyName
. Сейчас этот путь отображается какKeyPath<ParentTypeName, PropertyTypeName>
. - SE-0381: DiscardingTaskGroups позволит создавать группы задач, отбрасывающие дочерние задачи сразу после их завершения вместо явного использования
await
.
Возможно эти фичи будут доступны в окончательной версии Xcode 14.3, но это не точно.