Singleton Pattern
What is a Singleton?
A Singleton ensures only one instance of a class exists. In modern Swift, use actors for thread-safe singletons:
actor DatabaseManager {
static let shared = DatabaseManager()
private var database: Database?
private init() {}
func connect() async throws {
guard database == nil else { return }
database = try await Database.connect()
}
}
When to Use Singletons
- System services (networking, database, analytics)
- Shared resources (file manager, cache)
- Configuration objects
Avoid singletons for business logic, view models, and test doubles.
Observer Pattern
NotificationCenter
The Observer pattern allows objects to subscribe to notifications without tight coupling:
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
}
NotificationCenter.default.post(name: .userDidLogin, object: nil, userInfo: ["userId": user.id])
Combine Publishers
class UserModel: ObservableObject {
@Published var name: String = ""
@Published var isLoggedIn: Bool = false
}
struct ProfileView: View {
@ObservedObject var user: UserModel
var body: some View { Text(user.name) }
}
When to Use Each
- NotificationCenter: loose coupling, cross-module
- Combine: type-safe, composable, integrated with SwiftUI
Factory and Builder Patterns
Factory Pattern
Creates objects without specifying exact class:
protocol ViewFactory {
func makeView() -> UIViewController
}
struct HomeViewFactory: ViewFactory {
func makeView() -> UIViewController {
HomeViewController(viewModel: HomeViewModel())
}
}
Builder Pattern
Constructs complex objects step by step:
class URLRequestBuilder {
private var request: URLRequest
init(url: URL) { self.request = URLRequest(url: url) }
func method(_ method: String) -> URLRequestBuilder {
request.httpMethod = method; return self
}
func header(_ key: String, value: String) -> URLRequestBuilder {
request.setValue(value, forHTTPHeaderField: key); return self
}
func build() -> URLRequest { request }
}
Strategy Pattern
Defines interchangeable algorithms:
protocol SortStrategy {
func sort<T: Comparable>(_ array: inout [T])
}
struct QuickSort: SortStrategy {
func sort<T: Comparable>(_ array: inout [T]) { array.sort() }
}
Quiz
1. When should you use the Singleton pattern?
2. What problem does the Factory pattern solve?
3. How does the Builder pattern differ from Factory?
4. What is the Strategy pattern used for?
Flashcards
Question
What is the Singleton pattern?
Click to reveal answer
Answer
Provides a single shared instance of a class for global access, like NetworkManager.shared.
Question
When should you use the Factory pattern?
Click to reveal answer
Answer
When you need to create objects without specifying the exact class, enabling dependency inversion.
Question
How does the Builder pattern work?
Click to reveal answer
Answer
It constructs complex objects step by step using a fluent API, allowing flexible configuration.
Revision Notes
Key Takeaways
- 1. Singletons are useful for shared resources but can create tight coupling
- 2. Factory pattern enables dependency inversion and testability
- 3. Builder pattern provides flexible object construction
- 4. Strategy pattern allows swapping algorithms at runtime
Interview Tips
- • Explain when to use Singleton vs Dependency Injection
- • Discuss how Factory pattern improves testability
- • Describe the Builder pattern with a real-world example
- • Know the difference between Observer and Delegate patterns
Cheat Sheet
Design Patterns Quick Reference
- Singleton: shared instance
- Observer: NotificationCenter, KVO
- Factory: creates objects via protocols
- Builder: step-by-step construction
- Strategy: interchangeable algorithms