Swift
Completed
-
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
167: The Strategy Pattern with Protocols
Why can't I just use a switch statement to handle different behaviors?
You absolutely can, and for a tiny project, you probably should. But here's the problem: as your app grows, that switch statement becomes a magnet for every single change in your business logic. If you're handling payment methods—say, Credit Card, PayPal, and Apple Pay—every time the marketing team wants to add "Klarna" or "Crypto," you have to go back into your core checkout logic and add another case.
That's a violation of the Open/Closed Principle (entities should be open for extension, but closed for modification). I've spent way too many hours debugging "regression bugs" where adding a new case to a massive switch statement accidentally broke an existing one because they shared some weird local state. The Strategy Pattern lets you pull those behaviors out into their own isolated objects. You stop asking "What type is this?" and start saying "I don't care what you are, just perform your version of this action."
How do I actually implement this using protocols?
In Swift, the protocol is your "Strategy" interface. It defines the contract that every specific algorithm must follow. Let's look at a payment example. First, we define what it means to be a payment strategy, then we create the concrete implementations.
protocol PaymentStrategy {
func collectPayment(amount: Double)
}
struct CreditCardPayment: PaymentStrategy {
let cardNumber: String
func collectPayment(amount: Double) {
print("Charging $\(amount) to card \(cardNumber)...")
}
}
struct PayPalPayment: PaymentStrategy {
let email: String
func collectPayment(amount: Double) {
print("Redirecting to PayPal for \(email) to pay $\(amount)...")
}
}
Now, you need a "Context" object. This is the class that actually uses the strategy. The key here is that the CheckoutManager doesn't know which payment method it's using; it only knows that whatever it has conforms to PaymentStrategy.
class CheckoutManager {
private var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func setStrategy(_ strategy: PaymentStrategy) {
self.strategy = strategy
}
func executePayment(amount: Double) {
strategy.collectPayment(amount: amount)
}
}
Can I swap these strategies while the app is running?
Yes, and that's where this pattern really shines. Because the CheckoutManager holds the strategy as a property rather than hard-coding the logic, you can swap it on the fly based on user input.
Imagine a user is on the checkout screen. They start with Credit Card selected, but then realize they have a PayPal balance. You don't need to destroy your checkout object and recreate it; you just inject a different strategy.
// Start with Credit Card
let cardPayment = CreditCardPayment(cardNumber: "1234-5678-9012")
let checkout = CheckoutManager(strategy: cardPayment)
checkout.executePayment(amount: 49.99)
// User changes their mind to PayPal
let paypalPayment = PayPalPayment(email: "dev@example.com")
checkout.setStrategy(paypalPayment)
checkout.executePayment(amount: 49.99)
I personally love this approach because it makes unit testing trivial. You can create a MockPaymentStrategy that doesn't actually hit a network API, inject it into your manager, and test your checkout flow without spending real money or relying on a flaky sandbox server.
📋 Practical Task
Build a Dynamic Discount Engine
You are building a shopping cart for an e-commerce app. The app needs to apply different discount strategies based on the customer's status: VIP (20% off), Seasonal (10% off), and NoDiscount (0% off).
- Create a protocol named
DiscountStrategywith a methodapplyDiscount(to amount: Double) -> Double. - Implement three structs conforming to this protocol:
VIPDiscount,SeasonalDiscount, andNoDiscount. - Create a
ShoppingCartclass that maintains aDiscountStrategyproperty. - Add a method to
ShoppingCartcalledcalculateTotal(originalPrice: Double)that uses the current strategy to return the final price. - In your code, instantiate a
ShoppingCartwithNoDiscount, calculate a total, then switch the strategy toVIPDiscountand calculate the total again for the same price.
There are no comments for now.