Site icon AppTractor

Флаги функций в Swift

Почти в каждом проекте, над которым я работаю, есть как минимум три конфигурации сборки: 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 разработкой они позволяют мерджить работу на ранних этапах и часто не беспокоясь о прерывании выпуска. Вы можете скрыть незавершённые функции за флагами, безопасно тестировать их в отладочных и тестовых сборках и включать их только тогда, когда будете уверены в их готовности.

Источник

Exit mobile version