Мы рады открыть исходный код совершенно новой системы управления зависимостями для приложений на Swift. Она позволяет легко распространять зависимости вглубь вашего приложения эргономичным, но в то же время безопасным способом. Как только вы начнете управлять ими, то сразу же сможете писать более простые тесты, обнаруживать в превью Xcode мощь, не имеющую себе равных, улучшать время компиляции и многое другое.
Присоединяйтесь к нам, чтобы получить краткий обзор и подумать над добавлением библиотеки в свое приложение уже сегодня!
Обзор
Зависимости — это типы и функции в вашем приложении, которым нужно взаимодействовать с внешними системами, которые вы не контролируете. Классическими примерами этого являются API-клиенты, делающие сетевые запросы к серверам; но и безвредные на вид вещи, такие как инициализаторы UUID и Date, доступ к файлам, пользовательские настройки по умолчанию, и даже часы и таймеры - все это можно рассматривать как зависимости.
Вы можете уйти очень далеко в разработке приложений, даже не задумываясь об управлении зависимостями (или, как некоторые любят это называть, «внедрении зависимостей»), но в итоге неконтролируемые зависимости способны вызвать множество проблем в вашей кодовой базе и цикле разработки:
Неконтролируемые зависимости затрудняют написание быстрых детерминированных тестов, т.к. вы будете восприимчивы к капризам внешнего мира, таким как файловые системы, сетевое подключение, скорость интернета, время безотказной работы сервера и многое другое.
Многие зависимости плохо работают в превью SwiftUI, таких как менеджеры геолокации и распознаватели речи, а некоторые не работают даже в симуляторах, таких как менеджер движения и т. д. Что лишает вас возможности легко повторить дизайн технических характеристик этих фреймворков, если вы их используете.
Зависимости, которые взаимодействуют со сторонними библиотеками, не принадлежащими Apple (например, Firebase, библиотеки веб-сокетов, сетевые библиотеками, библиотеками потокового видео и т. д.), как правило, много весят и дольше компилируются. Что способно замедлить цикл вашей разработки.
По этим и многим другим причинам, крайне рекомендуем вам взять ваши зависимости под контроль, а не позволять им управлять вами.
Но управление зависимостью — это лишь начало. Как только вы берете их под контроль, вы сталкиваетесь с целым набором новых проблем:
Как вы распространите зависимости по всему приложению таким способом, чтобы это было более эргономично, чем явно передавать их повсюду, при этом - безопаснее, чем иметь глобальную зависимость?
Как вы переопределите зависимости только для одной части вашего приложения? Это может быть сподручно(*удобно) - переопределять зависимости в тестах и в превью SwiftUI, а также в процессах, относящихся к пользователям, таких как введение в продукт.
Как вы можете быть уверены, что переопределили все зависимости, используемые функцией в тестах? Было бы неправильно одни зависимости подделать для теста, но другие оставить открытыми для взаимодействия с внешним миром.
Эта библиотека решает все вышеперечисленные вопросы и многое, многое другое.
Применение вашей первой зависимости
Библиотека позволяет вам регистрировать ваши собственные зависимости, но она также предоставляет множество управляемых зависимостей прямо “из коробки" (полный список см. в DependencyValues), и есть большая вероятность, что вы сразу же сможете применить одну из них. Если вы используете планировщики Date(), UUID(), Task.sleep или Combine непосредственно в логике вашей функции, то вы уже можете начать использовать эту библиотеку.
Любое место, где вы используете одну из этих зависимостей непосредственно в логике функции, не передавая её в функцию явно, может быть обновлено с помощью обертки свойств @Dependency, чтобы сначала объявить вашу зависимость в функции:
final class FeatureModel: ObservableObject {
@Dependency(\.continuousClock) var clock // Controllable async sleep
@Dependency(\.date.now) var now // Controllable current date
@Dependency(\.mainQueue) var mainQueue // Controllable main queue scheduling
@Dependency(\.uuid) var uuid // Controllable UUID creation
// ...
}Как только ваши зависимости объявлены, вместо того, чтобы напрямую обращаться к Date(), UUID(), Task и т. д., вы можете использовать зависимость, определенную в модели вашей функции:
final class FeatureModel: ObservableObject {
// ...
func addButtonTapped() async throws {
try await self.clock.sleep(for: .seconds(1)) // 