-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
169: Practice Exercise: Building a Feature Flag System
We've all been there: you've spent two weeks building a shiny new "Premium Dashboard," but the Product Manager is nervous about flipping the switch for everyone at once. You need a way to toggle this feature on or off without submitting a new build to the App Store. The instinct here is to move fast, and when we move fast, we usually reach for the simplest tool available: a global singleton.
The Hardcoded Singleton Trap
In a naive implementation, you might create a FeatureFlags class with a shared instance and a handful of booleans. It looks like this:
class FeatureFlags {
static let shared = FeatureFlags()
var isPremiumDashboardEnabled = false
var isNewOnboardingFlowEnabled = true
private init() {}
}
// Usage in a ViewController
if FeatureFlags.shared.isPremiumDashboardEnabled {
showPremiumView()
} else {
showStandardView()
}
At first glance, this is great. It's easy to understand and takes thirty seconds to set up. But as I've seen in a few legacy projects, this approach quickly becomes a liability. The problem isn't the singleton itself, but the lack of abstraction. Your business logic is now tightly coupled to a global state. If you want to test how the app behaves when the dashboard is disabled, you have to manually mutate a global variable, which can lead to flaky tests if you aren't meticulously resetting the state between every single test case.
When Global State Breaks Your Testing
The real pain hits when you realize you can't easily mock these flags. Imagine you're writing a unit test for a DashboardCoordinator. You want to verify that the coordinator routes to the StandardView when the flag is off. With the singleton approach, your test is essentially fighting the rest of the app for control over FeatureFlags.shared. It's a nightmare for parallel testing, and it makes your code rigid. You aren't asking "Does this component behave correctly given this configuration?" instead, you're asking "Does this component behave correctly given the current global state of the entire application?"
Abstracting with Protocols and Enums
The professional way to handle this is to decouple the request for a flag from the source of the flag. I prefer using a protocol-oriented approach combined with a strongly typed enum for the features. This prevents typos that happen when using string keys and allows us to swap the provider depending on the environment.
enum Feature: String {
case premiumDashboard
case newOnboarding
}
protocol FeatureFlagProviding {
func isEnabled(_ feature: Feature) -> Bool
}
class RemoteFeatureFlagProvider: FeatureFlagProviding {
func isEnabled(_ feature: Feature) -> Bool {
// In a real app, this would check a local cache
// populated by a remote config service (like Firebase)
return false
}
}
class MockFeatureFlagProvider: FeatureFlagProviding {
var mockFlags: [Feature: Bool] = [:]
func isEnabled(_ feature: Feature) -> Bool {
return mockFlags[feature] ?? false
}
}
Now, instead of reaching for a singleton, you inject the FeatureFlagProviding protocol into your classes. Your view controller no longer knows where the flag comes from; it just knows that it can ask a provider if a specific feature is enabled. This makes your components incredibly easy to test. In your production code, you inject the RemoteFeatureFlagProvider; in your tests, you inject the MockFeatureFlagProvider and set the exact state you need for that specific test case. It's a bit more boilerplate up front, but it saves you from the "global state headache" as the project scales.
📋 Practical Task
Exercise: Implementing a Decoupled Feature Toggle for a 'Dark Mode Preview'
Your task is to move a hardcoded feature flag into a protocol-based system. You are building a "Dark Mode Preview" feature that should only be visible to beta testers.
Requirements:
- Create a
Featureenum that includes adarkModePreviewcase. - Define a
FeatureFlagProvidingprotocol with a methodisEnabled(_ feature: Feature) -> Bool. - Implement a
LocalFeatureFlagProviderthat returnstruefordarkModePreviewandfalsefor everything else. - Create a
SettingsViewModelclass that accepts aFeatureFlagProvidinginstance via its initializer. - Add a computed property to
SettingsViewModelcalledshouldShowDarkModeTogglethat returns the status of thedarkModePreviewflag using the injected provider. - Instantiate the
SettingsViewModelusing theLocalFeatureFlagProviderand print the value ofshouldShowDarkModeToggleto the console.
There are no comments for now.