Design Patterns in Swift: From Theory to Practice
Design patterns are proven solutions to common problems in development. For an iOS developer using Swift, mastering patterns is not just a plus on a resume, but a necessity. They help write clean, reusable, and testable code. In this article, we will break down 5 key patterns with real examples in Swift.
1. Singleton
One of the most popular patterns in iOS. It guarantees that a class has only one instance and provides a global point of access to it.
Implementation in Swift
class NetworkManager { static let shared = NetworkManager() private init() {} // prevents creating other instances
func requestData(from url: URL) { // request logic print("Request to \\(url.absoluteString)") }}
// UsageNetworkManager.shared.requestData(from: URL(string: "https://api.example.com")!)When to use: for managing shared resources (network, database, UserDefaults). Downside: complicates unit testing, so it should not be overused.
2. Factory Method
Defines an interface for creating an object but lets subclasses alter the type of object that will be created. It is perfect when it is not known in advance which specific object will be needed.
Example: creating UI elements
protocol Button { func render()}
class iOSButton: Button { func render() { print("iOS style button") }}
class macOSButton: Button { func render() { print("macOS style button") }}
enum Platform { case iOS, macOS}
class ButtonFactory { static func createButton(for platform: Platform) -> Button { switch platform { case .iOS: return iOSButton() case .macOS: return macOSButton() } }}
// Usagelet button = ButtonFactory.createButton(for: .iOS)button.render() // iOS style button3. Observer
Allows objects to subscribe to events and receive notifications about changes. In Swift, it is natively implemented via NotificationCenter and Combine.
Example with NotificationCenter
extension Notification.Name { static let userDidLogin = Notification.Name("userDidLogin")}
class AuthManager { func login() { // login logic NotificationCenter.default.post(name: .userDidLogin, object: nil) }}
class ProfileViewController { init() { NotificationCenter.default.addObserver( self, selector: #selector(updateUI), name: .userDidLogin, object: nil ) }
@objc func updateUI() { print("Updating profile after login") }
deinit { NotificationCenter.default.removeObserver(self) }}Modern alternative: use Combine (iOS 13+) with @Published and PassthroughSubject for reactive programming.
4. Delegate
One of the fundamental patterns of Cocoa Touch. It allows one object to delegate task execution to another. It is widely used in UIKit (e.g., UITableViewDelegate).
Creating a custom delegate
protocol DownloadDelegate: AnyObject { func didFinishDownloading(data: Data) func didFailWithError(error: Error)}
class Downloader { weak var delegate: DownloadDelegate?
func download(from url: URL) { // simulating download let data = Data() delegate?.didFinishDownloading(data: data) }}
class ViewController: UIViewController, DownloadDelegate { let downloader = Downloader()
override fu