Feature Flags в Swift: гибкая настройка без нового билда

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Feature Flags в Swift: как включать фичи без нового билда и не сломать продакшн

Представьте: вы выкатили новую тёмную тему, а пользователи пишут, что текст стал нечитаемым. Или добавили сложный алгоритм рекомендаций, который на старых iPhone работает с лагами. В классическом сценарии вы правите код, собираете билд, проходите ревью, ждёте App Store — сутки, а то и больше. Всё это время баг живёт в продакшне.

Feature flags (или feature toggles) — это техника, которая позволяет включать и выключать функциональность приложения без деплоя нового кода. По сути, это условные операторы, которые проверяют значение флага, а значение хранится удалённо — на сервере или в Remote Config. Один переключатель — и фича либо появляется у всех, либо исчезает за секунды.

В этой статье мы разберём, как внедрить feature flags в Swift, какие паттерны использовать, и как не выстрелить себе в ногу. Поехали!

Зачем нужны feature flags в iOS-разработке

Многие думают, что фича-флаги — это только про A/B тесты. На самом деле сценариев гораздо больше:

  • Канареечные релизы (canary releases) — включаете новую фичу для 5% пользователей, мониторите краши, и если всё ок — раскатываете на всех.
  • Киллер-фичи (kill switches) — если что-то пошло не так, вы просто отключаете функционал удалённо, без отката билда.
  • Поэтапный роллаут — показываете фичу сначала iOS 17+, потом более старым версиям.
  • Платные фичи — скрываете премиум-функции за подпиской, управляя флагами на бэкенде.
  • Эксперименты и A/B тесты — проверяете гипотезы на реальных пользователях, не создавая отдельные ветки.

Особенно это актуально для Swift-проектов, где бинарник собирается один раз, а потом лежит в сторе. Без фича-флагов вы не сможете быстро отреагировать на проблемы — только через новый релиз.

Архитектура feature flags в Swift

Простая реализация — это словарь [String: Bool], который приходит с сервера. Но в боевом проекте лучше сделать нормальную абстракцию. Вот ключевые компоненты:

  1. FeatureFlagProvider — источник данных (Remote Config, ваш API, локальный файл).
  2. FeatureFlagManager — синглтон или сервис, который кеширует флаги и отдаёт их вьюхам.
  3. FeatureFlagKey — enum с типизированными ключами, чтобы не размазывать строки по коду.

Давайте напишем простую, но рабочую реализацию на Swift.

Шаг 1. Определяем enum с ключами

enum FeatureFlagKey: String, CaseIterable {
    case darkMode = "dark_mode"
    case newOnboarding = "new_onboarding"
    case premiumAnalytics = "premium_analytics"
    case experimentalFeed = "experimental_feed"
}

Использование enum вместо строк гарантирует, что вы не опечатаетесь в названии флага. CaseIterable пригодится для дебажного экрана.

Шаг 2. Провайдер — получаем флаги из Firebase Remote Config

import FirebaseRemoteConfig

final class RemoteConfigProvider: FeatureFlagProvider {
    private let remoteConfig = RemoteConfig.remoteConfig()
    
    func fetchFlags(completion: @escaping ([String: Bool]) -> Void) {
        remoteConfig.fetch(withExpirationDuration: 3600) { status, error in
            if status == .success {
                self.remoteConfig.activate { _, _ in
                    let flags = FeatureFlagKey.allCases.reduce(into: [String: Bool]()) { result, key in
                        result[key.rawValue] = self.remoteConfig.configValue(forKey: key.rawValue).boolValue
                    }
                    completion(flags)
                }
            } else {
                print("Failed to fetch remote config: \(error?.localizedDescription ?? "")")
                completion([:])
            }
        }
    }
}

Здесь мы используем Firebase — стандартный выбор для iOS. Но можно подставить любой другой сервис: LaunchDarkly, Split, Unleash или свой бэкенд.

Шаг 3. Менеджер — единая точка доступа

final class FeatureFlagManager {
    static let shared = FeatureFlagManager()
    
    private var flags: [String: Bool] = [:]
    private let provider: FeatureFlagProvider
    
    private init(provider: FeatureFlagProvider = RemoteConfigProvider()) {
        self.provider = provider
    }
    
    func loadFlags() {
        provider.fetchFlags { [weak self] fetchedFlags in
            self?.flags = fetchedFlags
        }
    }
    
    func isEnabled(_ key: FeatureFlagKey) -> Bool {
        return flags[key.rawValue] ?? false
    }
}

Метод isEnabled возвращает дефолтное значение false, если флаг не найден. Это безопасно — новая фича не включится сама собой.

Шаг 4. Используем в коде

if FeatureFlagManager.shared.isEnabled(.darkMode) {
    view.overrideUserInterfaceStyle = .dark
} else {
    view.overrideUserInterfaceStyle = .light
}

Всё просто. Но это только начало. Настоящая мощь раскрывается, когда вы начинаете комбинировать флаги с аудиториями и процентами.

Продвинутые сценарии: проценты, аудитории и A/B тесты

Простое true/false — это база. Но в реальности вам может понадобиться включить фичу для 20% пользователей, или только для тех, кто купил подписку, или для жителей определённого региона.

Для этого флаги хранят не просто булево значение, а JSON с условиями. Пример структуры:

{
    "dark_mode": {
        "enabled": true,
        "rollout_percentage": 50,
        "target_audience": {
            "min_app_version": "2.3.0",
            "countries": ["RU", "BY"]
        }
    }
}

Обработка на клиенте:

func isDarkModeEnabled(for user: User) -> Bool {
    guard let config = flags["dark_mode"] else { return false }
    guard config.enabled else { return false }
    guard config.rolloutPercentage > Double.random(in: 0...100) else { return false }
    guard config.targetAudience.countries.contains(user.country) else { return false }
    return true
}

Важно: случайное число генерируется один раз при старте сессии и сохраняется в UserDefaults, чтобы пользователь не прыгал между вариантами при каждом открытии экрана.

Безопасность и тестирование feature flags

Фича-флаги — мощный инструмент, но без дисциплины они превращают код в лапшу. Вот несколько правил, которые мы используем в продакшн-проектах:

1. Никаких вложенных флагов

Не проверяйте один флаг внутри другого. Это делает систему непредсказуемой. Если нужно — создайте отдельный флаг для комбинации.

2. Удаляйте старые фла

Блоги

Рекомендаций по книгам