Advanced Design Patterns in Swift: From Theory to Practice
Design patterns are not just abstract concepts, but time-tested solutions to common problems. If you have already mastered basic patterns (Singleton, Delegate, MVC) and are working on complex iOS/macOS applications, it's time to move to the next level. In this article, we will cover four advanced patterns: Builder, Strategy, Observer (via Combine) and Coordinator. Each pattern will be illustrated with real Swift code and tips for integrating into existing projects.
1. Builder — flexible configuration of complex objects
The Builder pattern is ideal for creating objects with many optional parameters. Instead of long initializers with dozens of parameters, you use step-by-step construction.
Example: configuring UIAlertController
class AlertBuilder { private var title: String? private var message: String? private var actions: [UIAlertAction] = [] private var preferredStyle: UIAlertController.Style = .alert
func setTitle(_ title: String) -> AlertBuilder { self.title = title return self }
func setMessage(_ message: String) -> AlertBuilder { self.message = message return self }
func addAction(title: String, style: UIAlertAction.Style = .default, handler: ((UIAlertAction) -> Void)? = nil) -> AlertBuilder { let action = UIAlertAction(title: title, style: style, handler: handler) actions.append(action) return self }
func setStyle(_ style: UIAlertController.Style) -> AlertBuilder { self.preferredStyle = style return self }
func build() -> UIAlertController { let alert = UIAlertController(title: title, message: message, preferredStyle: preferredStyle) actions.forEach { alert.addAction($0) } return alert }}
// Usagelet alert = AlertBuilder() .setTitle("Error") .setMessage("Failed to load data") .addAction(title: "OK", style: .default) .addAction(title: "Retry", style: .cancel) .build()present(alert, animated: true)When to use: when creating configurators, complex ViewControllers or data models with many options.
2. Strategy — switching algorithms on the fly
The Strategy pattern allows you to isolate a family of algorithms and make them interchangeable. This is especially useful for payment processing, data validation, or rendering.
Example: content filtering system
protocol FilterStrategy { func apply(to items: [String]) -> [String]}
class AlphabeticalFilter: FilterStrategy { func apply(to items: [String]) -> [String] { return items.sorted() }}
class LengthFilter: FilterStrategy { func apply(to items: [String]) -> [String] { return items.filter { $0.count > 5 } }}
class ContentManager { private var strategy: FilterStrategy
init(strategy: FilterStrategy) { self.strategy = strategy }
func setStrategy(_ strategy: FilterStrategy) { self.strategy = strategy }
func filter(items: [String]) -> [String] { return strategy.apply(to: items) }}
// Usagelet manager = ContentManager(strategy: AlphabeticalFilter())let data = ["Swift", "Objective-C", "Kotlin", "Java", "Python"]print(manager.filter(items: data)) // ["Java", "Kotlin", "Objective-C", "Python", "Swift"]
manager.setStrategy(LengthFilter())print(manager.filter(items: data)) // ["Objective-C", "Python"]Tip: combine Strategy with a factory method to select a strategy based on user settings.