Почти в каждом проекте, над которым я работаю, есть как минимум три конфигурации сборки: Debug, TestFlight и App Store. Эти конфигурации различаются не только настройками сборки, но и функциональностью. В этой статье мы узнаем, как реализовать флаги функций (фичефлаги) в Swift, которые позволяют включать и отключать определённые функции при определённых условиях.
Будучи большим поклонником trunk-based разработки, я считаю, что флаги функций играют ключевую роль в моём подходе к разработке. Практически каждая функция, над которой я работаю в последнее время, имеет флаг, активирующий её в отладочных и тестовых сборках. Применяя подход на основе основной ветки (trunk), я объединяю свои ветки, даже если функция реализована не полностью, поэтому я использую флаги функций для их временного отключения.
По умолчанию любой проект Xcode имеет две конфигурации: Debug и Release. Вы можете создать столько конфигураций, сколько вам нужно, и я всегда создаю дубликаты для конфигурации Release с именами AppStore и TestFlight. Это позволяет создавать пользовательские схемы Xcode, работающие с одной из доступных конфигураций. Затем мы можем использовать условия компиляции в коде, чтобы определить, какая схема активна в данный момент.
Начнём с создания перечисления Distribution, определяющего наши схемы в Xcode.
public enum Distribution: Sendable {
case debug
case appstore
case testflight
}
extension Distribution {
static var current: Self {
#if APPSTORE
return .appstore
#elseif TESTFLIGHT
return .testflight
#else
return .debug
#endif
}
}
Как видно из примера выше, тип Distribution довольно прост. Мы определяем статическое свойство current, которое переключает условия компиляции для поиска активного. Теперь можно перейти к типу FeatureFlags, который должен определять функции, над которыми я сейчас работаю, или некоторые флаги конфигурации.
public struct FeatureFlags: Sendable, Decodable {
public let requirePaywall: Bool
public let requireOnboarding: Bool
public let featureX: Bool
public init(distribution: Distribution) {
switch distribution {
case .debug:
self.requirePaywall = true
self.requireOnboarding = true
self.featureX = true
case .appstore:
self.requirePaywall = true
self.requireOnboarding = true
self.featureX = false
case .testflight:
self.requirePaywall = false
self.requireOnboarding = true
self.featureX = true
}
}
}
Тип FeatureFlags определяет набор свойств, которые я включаю и выключаю для разных типов сборки. Как видите, функция init принимает экземпляр типа Distribution, что позволяет мне передавать активный дистрибутив.
Работая в Xcode, я выбираю конфигурацию отладки, поскольку она не содержит слишком много оптимизаций компилятора, быстро собирается и позволяет мне видеть, над чем я работаю. Именно поэтому я включаю все флаги для отладочного состояния.
Я хочу предоставить пользователям TestFlight доступ ко всем функциям, даже платным, чтобы быть уверенным, что всё работает правильно. Поэтому я отключаю платный доступ для пользователей TestFlight.
extension EnvironmentValues {
@Entry public var featureFlags = FeatureFlags(distribution: .debug)
}
Последним шагом является размещение экземпляра типа FeatureFlags в среду SwiftUI для его совместного использования в иерархии представлений, чтобы мои представления могли отключать или включать определенные функции.
@main
struct CardioBotApp: App {
var body: some Scene {
WindowGroup {
RootView()
.environment(\.featureFlags, FeatureFlags(distribution: .current))
}
}
}
Флаги функций не являются постоянными. Относитесь к ним как к временным помощникам — удаляйте их, когда функция будет готова и проверена. А если ваш проект разрастётся, рассмотрите возможность удалённой настройки для мгновенного развёртывания или отката функций. Такой подход обеспечит быстрый, безопасный и готовый к непрерывной поставке процесс разработки.
В сочетании с trunk-based разработкой они позволяют мерджить работу на ранних этапах и часто не беспокоясь о прерывании выпуска. Вы можете скрыть незавершённые функции за флагами, безопасно тестировать их в отладочных и тестовых сборках и включать их только тогда, когда будете уверены в их готовности.

