1-летняя годовщина: Краткое содержание на GitHub! AsyncStream, прикрепленные макросы (в SwiftPM), публикация “пакетов”, наборы параметров, фиче-флаги и предварительный просмотр Foundation.
Прошел уже целый год с тех пор, как я начал свой блог! 🥳 Вы можете в это поверить? Я получил столько удовольствия, делясь с вами всеми последними новостями и обновлениями Swift за прошедший год. Начав с краткого изложения истории Swift Evolution в первом посте, мне удалось описать уже целых 44 предложения читателей, а в этом выпуске я добавляю еще шесть, достигая 50!
Чтобы отпраздновать это достижение, я создал проект на GitHub, там находятся все мои прошлые проекты, которые были описаны в моем блоге. Это должно значительно облегчить любому члену сообщества поиск конкретного проекта. Я создал репозиторий таким образом, чтобы вы могли просто заменить apple в URL-адресе на GitHub
(https://github.com/apple/swift-evolution) с помощью FlineDev, и вы сразу перейдете к краткому изложению определенного проекта!
Я также решил, что, начиная с этого поста, я всегда буду обобщать старые проекты, которые были изучены и опубликованы. Таким образом, репозиторий должен стать еще более полезным и актуальным. И я приглашаю членов сообщества, которые уже написали свои предложения, тоже внести свой вклад в проект! Или просто напишите статью по теме, которая вас всегда интересовала. Написание статьи - отличный способ узнать больше о Swift, могу сказать по опыту! 😉
Большое вам спасибо за то, что до сих пор являетесь частью этого путешествия вместе со мной. Выпьем за еще один замечательный год Swift Evolution Monthly! 🍻 🎉
Принятые предложения
Первое из недавно принятых предложений в значительной степени связано с более старым предложением, которое я еще не рассматривал. Итак, я думаю, что наиболее естественно начать с этого старого предложения:
SE-0314: AsyncStream и AsyncThrowingStream
Ссылки 📝: Предложение | 💬 Ревью: Первое и второе | ✅ Принято
Мы все использовали множество типов, соответствующих протоколу Sequence, таких как Array, Dictionary или String. Это позволяет легко выполнять итерацию с помощью for element в массиве. Такая же обработка была добавлена в новый мир Swift Concurrency с помощью AsyncSequence, который в значительной степени совпадает с Sequence, но значения доступны не сразу, вместо этого они приходят позже. Но использование выглядит точно так же, только добавлено ключевое слово await: for await element in array.
На самом деле, в только что приведенном примере кода есть одна ошибка: array. Поскольку тип Array не соответствует AsyncSequence, вместо этого нам нужны новые типы, которые мы можем создавать и возвращать в наших API или которые мы можем использовать из системных API. И вот тут-то в игру вступают AsyncStream и AsyncThrowingStream. Если вы не знакомы с потоками, думайте о них как об "асинхронных массивах". Конечно, это упрощение, и создание AsyncStream сильно отличается от создания массива. Вот простой пример:
let swiftFileURL: URL = ...
let matches: (String) -> Bool = { $0.contains("// TODO:") }
let matchingLineNumbersStream = AsyncStream
Task {
var lineNumber: Int = 0
for try await line in swiftFileUrl.lines {
lineNumber += 1
if matches(line) {
continuation.yield(lineNumber)
}
}
continuation.finish()
}
}
Обратите внимание, что нам передается continuation, которому мы передаем новые значения, вызывая yield(), и когда мы достигаем конца "асинхронного массива", мы вызываем .finish(). Таким образом, нам не нужно писать пользовательскую логику итератора.
Тогда потребители, которым нравится какое-то правило в инструменте linting, могли бы использовать поток следующим образом:
Самое замечательное в AsyncStream то, что он полезен не только для медленных API, таких как вызов данных с сервера. Но он также может быть использован при получении значений очень быстро, на самом деле быстрее, чем мы можем использовать при вызове. AsyncStream автоматически буферизует значения в этом случае и отправляет их в порядке получения, поэтому мы никогда не пропускаем никаких значений, и все работает так, как ожидалось.
SE-0388: Удобный метод AsyncStream.makeStream
Ссылки 📝: Предложение | 💬 Ревью | ✅ Принято
В настоящее время при создании AsyncStream вам передается параметр continuation в последующее замыкание для отправки значений (или .yield). В некоторых случаях это работает нормально. Но во многих ситуациях вы действительно можете захотеть передать continuation в другие места. И это предложение добавляет эту возможность:
let (stream, continuation) = AsyncStream.makeStream(of: Int.self)
await withTaskGroup(of: Void.self) { group in
group.addTask {
// code using `continuation`
}
group.addTask {
// code using `stream`
}
}
Обратите внимание на метод makeStream(of:), возвращающий кортеж, содержащий continuation. Также стоит отметить, что при этом используется @backDeployed(before:), недавно представленный в SE-0376, чтобы сделать этот API доступным для проектов, ориентированных на более старые версии ОС, чем те, в которые будет включен Swift 5.9. 💯
SE-0389: Прикрепленные макросы
Ссылки 📝: Предложение | 💬 Ревью | ✅ Принято | 🔮Видение
Это продолжение SE-0382, в котором были введены "Макросы". Прошлая статья была сосредоточена на более функциональной структуре, которую мы могли бы "вызывать" из любого места, используя #, это предложение фокусируется на "привязке" поведения к существующим объявлениям, таким как функции или типы, используя @. Это может напомнить вам о SE-0258, в котором были введены оболочки свойств, позволяющие привязывать дополнительное поведение к хранимым свойствам, как это сделано в SwiftUI с помощью @State. И, на самом деле, это сходство намеренно, потому что, в некоторой степени, "прикрепленные макросы" являются расширением оболочек свойств для всех видов объявлений и явно указаны как "включающие в себя некоторые поведения".
Например, вы могли бы объявить "одноранговый макрос" с именем AddCompletionHandler:
@attached(peer, names: overloaded)
macro AddCompletionHandler(param: String = "completionHandler")
Затем прикрепите его к любому асинхронному API, подобному этому (переопределяя имя параметра по умолчанию):
@AddCompletionHandler(param: "completion")
func fetchImage(url: URL) async throws -> Image { ... }
Макрос создаст перегруженную версию той же функции с non-asynccompletionhandler в качестве параметра для вас, например:
func fetchImage(url: URL, completion: (Image) -> Void) throws {
Task {
let image = try await self.fetchImage(url)
completion(image)
}
}
Реализация макроса немного сложна и требует понимания синтаксиса и нового модуля макросов Swift Syntax. Но сама по себе возможность корректировать наш Swift-код с помощью Swift-кода является очень мощной.
В общей сложности будет 5 различных видов прикрепленных макросов:
- Одноранговые макросы: Прилагается к декларациям для предоставления дополнительных деклараций.
- Макрос-участник: Прикрепляется к типам для добавления новых элементов (например, свойств).
- Макросы доступа: Прикрепляется к хранимым свойствам, чтобы превратить их в вычисляемые свойства с полным доступом к локальному контексту (в отличие от оболочек свойств).
- Макросы атрибутов элементов: Прикрепляется к типам для применения атрибутов ко всем элементам (аналогично @objmembers или @MainActor).
- Макросы соответствия: Прикрепляется к типам для добавления новых соответствий протоколу.
Мне действительно понравилось, как Swift улучшил работу опыт для разработчиков, автоматически синтезируя соответствие таким типам, как Codable (SE-0166 + SE-0295), Hashable или Equatable (оба SE-0185). И я действительно рад видеть, что теперь мы сами получаем возможность внедрять подобные волшебные API. 🪄
SE-0391: Публикация реестра пакетов
Ссылки 📝: Предложение | 💬 Ревью | ✅ Принято
Это предложение определяет все, чего не хватает для обеспечения единого способа публикации пакетов Swift в реестрах, таких как теперь официально поддерживаемый Apple Package Index:
- Формат метаданных для связанных URL-адресов, авторов и организаций.
- Формат подписи пакета для реестров, требующих подписи.
- Новая подкоманда package-registry publish для "создания архива исходного кода пакета, подписания его, если необходимо, и публикации в реестре" - все это за один раз.
- Набор требований к реестрам, поддерживающим подписание, для обеспечения безопасности.
Хорошей новостью является то, что вам, вероятно, не придется больше узнавать о деталях этого предложения благодаря великолепной работе Дейва и Свена, а также поддержке сообщества Swift, включая этих 85 удивительных людей. Все, что может понадобиться авторам фреймворка, - это новая подкоманда publish, а может и меньше, если они используют CI для загрузки, потому что Apple может отправить пользовательский интерфейс для этого в Xcode. 🤞
SE-0393: Пакеты параметров значений и типов
Ссылки 📝: Предложение | 💬 Ревью | ✅ Принято
Это предложение добавляет два новых ключевых слова в язык Swift:
- each: предшествует типу (например, some) и помечает его как "пакет параметров".
- repeat: предшествует type/expression и "расширяет" соответствующий пакет.
Но что такое "пакет параметров" и что значит "расширить" его?
В Swift при написании функций, которые принимают n-число значений (потенциально) разных типов, лучшее, что мы можем сделать, если не хотим удалять их типы - это написать универсальную функцию, подобную этой:
func areEquivalent(_ first: A, _ second: B, _ third: C) -> Bool
// example usage
areEquivalent("42", 42, 42.0) // => true
Хоть эта функция использует generic, чтобы нам не нужно было перегружать одну и ту же функцию с разными спецификациями типов, она ограничена приемом ровно трех параметров. Но что, если бы нам понадобилась areaEquivalent реализация для любого количества различных типов? Чтобы поддерживать до 10 параметров, нам нужно было бы определить 10 различных универсальных методов, подобных приведенным выше, каждый с растущим числом универсальных типов. Но не могли бы мы как-нибудь попросить Swift сделать это за нас?
Что ж, это именно то, что делает это предложение с помощью каждого и повторения:
func areEquivalent
"Пакет параметров" - это то, что позволяет функции принимать произвольное количество аргументов любого типа, здесь: each T. "Расширение" пакета - это процесс распаковки его содержимого в отдельные аргументы, здесь: repeat each T. Приведенное выше объявление одной функции "расширено" Swift до объявлений функций с нулем, одним, двумя, тремя и более параметрами (потенциально) различных типов.
И это еще не все, в этом предложении есть гораздо больше – на самом деле оно довольно подробное. Приведенное выше - всего лишь краткое введение в пакеты параметров, но оно даже не показывает их реализацию. Вот реализация из этого предложения:
И это, кажется, только первый шаг к более широкой теме программирования с вариативными обобщениями, соответствующий шаг и соответствующее предложение перечислены далее ниже.
SE-0394: Пакет “Manager Support” для пользовательских макросов
Ссылки 📝: Предложение | 💬 Ревью | ✅ Принято | 🔮Видение
С SE-0382 "Макросы выражений" и SE-0389 "Прикрепленные макросы" ( ⬆️ ) мы приближаемся к миру Swift, полному макросов. Но что, если бы мы захотели поделиться макросами между проектами или с сообществом в пакете с открытым исходным кодом?
Это предложение добавляет новый целевой тип в манифест пакета Swift с именем macro, который имеет ту же форму, что и другие целевые типы, такие как target или testTarget. Но он ведет себя больше как целевой тип плагина, добавленный в SE-0303: при включении в проект целевые макросы создаются как исполняемые файлы, так что компилятор может запускать их как часть процесса компиляции для любых зависящих от них целевых объектов. Кроме того, как и плагины пакета, целевой макрос будет выполняться в изолированной среде, которая предотвращает доступ к файловой системе и сети.
Я с нетерпением жду макросов, которые предложит сообщество, особенно если Apple представит, как использовать эти новые функции, более широкой аудитории на WWDC.