Files
obsidian-vault/work/wiki/apple-browsers/feature-flags.md
T

379 lines
10 KiB
Markdown

---
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/feature-flags.mdc
confidence: 0.9
namespace: work
last_synced: 2026-04-28
alwaysApply: false
---
# Feature Flag Patterns
## Type-Safe Feature Flags
Use enum-based feature flags with protocols for type safety:
```swift
// ✅ CORRECT - Type-safe feature flags
protocol FeatureFlag: RawRepresentable where RawValue == String {
var defaultValue: Bool { get }
var description: String { get }
}
enum UIFeatureFlag: String, FeatureFlag {
case newTabPageRedesign = "new_tab_page_redesign"
case advancedPrivacySettings = "advanced_privacy_settings"
case voiceSearch = "voice_search"
case experimentalUI = "experimental_ui"
var defaultValue: Bool {
switch self {
case .newTabPageRedesign: return false
case .advancedPrivacySettings: return true
case .voiceSearch: return false
case .experimentalUI: return false
}
}
var description: String {
switch self {
case .newTabPageRedesign:
return "Enable redesigned new tab page"
case .advancedPrivacySettings:
return "Show advanced privacy settings"
case .voiceSearch:
return "Enable voice search functionality"
case .experimentalUI:
return "Enable experimental UI components"
}
}
}
enum NetworkFeatureFlag: String, FeatureFlag {
case networkProtectionV2 = "network_protection_v2"
case enhancedBlocking = "enhanced_blocking"
var defaultValue: Bool {
switch self {
case .networkProtectionV2: return false
case .enhancedBlocking: return true
}
}
var description: String {
switch self {
case .networkProtectionV2:
return "Enable Network Protection V2"
case .enhancedBlocking:
return "Enhanced content blocking"
}
}
}
```
## FeatureFlagger Protocol Implementation
Extend the existing FeatureFlagger with type-safe methods:
```swift
// ✅ CORRECT - Type-safe FeatureFlagger extension
extension FeatureFlagger {
func isEnabled<Flag: FeatureFlag>(_ flag: Flag) -> Bool {
return isFeatureOn(flag.rawValue) ?? flag.defaultValue
}
func setEnabled<Flag: FeatureFlag>(_ enabled: Bool, for flag: Flag) {
setFeatureOn(flag.rawValue, enabled: enabled)
}
}
// Usage in code
final class FeatureViewModel: ObservableObject {
private let featureFlagger: FeatureFlagger
init(featureFlagger: FeatureFlagger) {
self.featureFlagger = featureFlagger
}
func loadContent() {
if featureFlagger.isEnabled(UIFeatureFlag.newTabPageRedesign) {
loadNewDesign()
} else {
loadLegacyDesign()
}
}
}
```
## Feature Flag ViewModifier
Create SwiftUI modifiers for conditional UI:
```swift
// ✅ ADVANCED - SwiftUI feature flag modifier
struct FeatureFlagModifier<Flag: FeatureFlag>: ViewModifier {
let flag: Flag
let featureFlagger: FeatureFlagger
let fallback: () -> AnyView
func body(content: Content) -> some View {
if featureFlagger.isEnabled(flag) {
content
} else {
fallback()
}
}
}
extension View {
func featureFlag<Flag: FeatureFlag>(
_ flag: Flag,
featureFlagger: FeatureFlagger,
@ViewBuilder fallback: @escaping () -> some View = { EmptyView() }
) -> some View {
modifier(FeatureFlagModifier(
flag: flag,
featureFlagger: featureFlagger,
fallback: { AnyView(fallback()) }
))
}
}
// Usage
struct ContentView: View {
@Environment(\.dependencies) var dependencies
var body: some View {
VStack {
NewFeatureView()
.featureFlag(UIFeatureFlag.experimentalUI,
featureFlagger: dependencies.featureFlagger) {
LegacyFeatureView()
}
}
}
}
```
## Feature Flag Property Wrapper
Create a property wrapper for reactive feature flags:
```swift
// ✅ ADVANCED - Reactive feature flag property wrapper
@propertyWrapper
struct FeatureFlagState<Flag: FeatureFlag>: DynamicProperty {
@ObservedObject private var flagger: ObservableFeatureFlagger
private let flag: Flag
var wrappedValue: Bool {
get { flagger.isEnabled(flag) }
nonmutating set { flagger.setEnabled(newValue, for: flag) }
}
var projectedValue: Binding<Bool> {
Binding(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}
init(_ flag: Flag) {
self.flag = flag
self._flagger = ObservedObject(wrappedValue: ObservableFeatureFlagger.shared)
}
}
// Usage in SwiftUI views
struct SettingsView: View {
@FeatureFlagState(UIFeatureFlag.voiceSearch) var voiceSearchEnabled
@FeatureFlagState(UIFeatureFlag.experimentalUI) var experimentalUIEnabled
var body: some View {
Form {
Toggle("Voice Search", isOn: $voiceSearchEnabled)
Toggle("Experimental UI", isOn: $experimentalUIEnabled)
}
}
}
```
## A/B Testing Integration
Integrate feature flags with A/B testing:
```swift
// ✅ ADVANCED - A/B testing with feature flags
enum ABTestVariant: String, CaseIterable {
case control = "control"
case variantA = "variant_a"
case variantB = "variant_b"
var displayName: String {
switch self {
case .control: return "Control Group"
case .variantA: return "Variant A"
case .variantB: return "Variant B"
}
}
}
protocol ABTestFeatureFlag: FeatureFlag {
var variants: [ABTestVariant] { get }
var currentVariant: ABTestVariant { get }
}
enum ExperimentalFeatureFlag: String, ABTestFeatureFlag {
case newOnboardingFlow = "new_onboarding_flow"
case redesignedSearch = "redesigned_search"
var defaultValue: Bool { true }
var description: String {
switch self {
case .newOnboardingFlow: return "New onboarding flow experiment"
case .redesignedSearch: return "Redesigned search interface experiment"
}
}
var variants: [ABTestVariant] {
[.control, .variantA, .variantB]
}
var currentVariant: ABTestVariant {
// Get variant from A/B testing service
return ABTestingService.shared.getVariant(for: self.rawValue)
}
}
// Usage with variants
func configureOnboarding() {
let experiment = ExperimentalFeatureFlag.newOnboardingFlow
switch experiment.currentVariant {
case .control:
showLegacyOnboarding()
case .variantA:
showNewOnboardingVariantA()
case .variantB:
showNewOnboardingVariantB()
}
// Track experiment exposure
PixelFiring.fire(.experimentExposure(experiment.rawValue, experiment.currentVariant.rawValue))
}
```
## Remote Feature Flags
Integrate with remote configuration:
```swift
// ✅ ADVANCED - Remote feature flag management
protocol RemoteFeatureFlag: FeatureFlag {
var remoteKey: String { get }
var localOverrideKey: String? { get }
}
extension RemoteFeatureFlag {
var remoteKey: String { rawValue }
var localOverrideKey: String? { "local_override_\(rawValue)" }
}
enum RemoteUIFeatureFlag: String, RemoteFeatureFlag {
case serverDrivenUI = "server_driven_ui"
case dynamicThemes = "dynamic_themes"
var defaultValue: Bool { false }
var description: String {
switch self {
case .serverDrivenUI: return "Server-driven UI configuration"
case .dynamicThemes: return "Dynamic theme system"
}
}
}
final class RemoteFeatureFlagger: FeatureFlagger {
private let remoteConfig: RemoteConfigProtocol
private let localDefaults: UserDefaults
init(remoteConfig: RemoteConfigProtocol, localDefaults: UserDefaults) {
self.remoteConfig = remoteConfig
self.localDefaults = localDefaults
}
func isEnabled<Flag: RemoteFeatureFlag>(_ flag: Flag) -> Bool {
// Check local override first
if let overrideKey = flag.localOverrideKey,
let localOverride = localDefaults.object(forKey: overrideKey) as? Bool {
return localOverride
}
// Check remote config
if let remoteValue = remoteConfig.boolValue(for: flag.remoteKey) {
return remoteValue
}
// Fall back to default
return flag.defaultValue
}
func setLocalOverride<Flag: RemoteFeatureFlag>(_ enabled: Bool?, for flag: Flag) {
guard let overrideKey = flag.localOverrideKey else { return }
if let enabled = enabled {
localDefaults.set(enabled, forKey: overrideKey)
} else {
localDefaults.removeObject(forKey: overrideKey)
}
}
}
```
## Debug Feature Flag Interface
Create debug interface for testing:
```swift
// ✅ DEBUG - Feature flag debug interface
#if DEBUG
struct FeatureFlagDebugView: View {
@StateObject private var debugFlags = DebugFeatureFlags()
var body: some View {
NavigationView {
List {
Section("UI Features") {
ForEach(UIFeatureFlag.allCases, id: \.rawValue) { flag in
FeatureFlagRow(flag: flag, debugFlags: debugFlags)
}
}
Section("Network Features") {
ForEach(NetworkFeatureFlag.allCases, id: \.rawValue) { flag in
FeatureFlagRow(flag: flag, debugFlags: debugFlags)
}
}
}
.navigationTitle("Feature Flags")
}
}
}
struct FeatureFlagRow<Flag: FeatureFlag>: View where Flag: CaseIterable {
let flag: Flag
@ObservedObject var debugFlags: DebugFeatureFlags
var body: some View {
Toggle(isOn: Binding(
get: { debugFlags.isEnabled(flag) },
set: { debugFlags.setEnabled($0, for: flag) }
)) {
VStack(alignment: .leading) {
Text(flag.rawValue)
.font(.headline)
Text(flag.description)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
#endif
```
See `analytics-patterns.md` for pixel firing patterns and `configuration-management.md` for advanced configuration management.