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
166: The Delegate Pattern In Depth
Why use a delegate instead of just passing in a closure?
I get this question a lot because, on the surface, closures seem easier. You just pass a block of code and you're done. But closures are best for "one-and-done" events—like a single network request finishing. When you have a complex relationship where one object needs to report multiple different types of events over a long period, a delegate is much cleaner.
Imagine we're building a MusicPlayer. It needs to tell the UI when the song starts, when it pauses, when it finishes, and when an error occurs. If you used closures, your MusicPlayer initializer would look like a nightmare of five or six different closure properties. With a delegate, you just define one protocol that groups all those behaviors together.
protocol MusicPlayerDelegate: AnyObject {
func musicPlayerDidStartPlaying(_ player: MusicPlayer)
func musicPlayerDidPause(_ player: MusicPlayer)
func musicPlayerDidFinishSong(_ player: MusicPlayer, songTitle: String)
func musicPlayerDidEncounterError(_ player: MusicPlayer, error: Error)
}
class MusicPlayer {
weak var delegate: MusicPlayerDelegate?
func play() {
// ... playback logic ...
delegate?.musicPlayerDidStartPlaying(self)
}
}
See how the MusicPlayer doesn't care who is listening? It just knows that whoever the delegate is, they've promised to handle those specific methods.
Why do I always see "weak var" used for the delegate?
If you forget the weak keyword here, you are almost certainly creating a retain cycle (a memory leak). Let's trace it: usually, your View Controller owns the MusicPlayer (a strong reference). If the MusicPlayer also has a strong reference back to the View Controller via the delegate, neither object can ever be deleted from memory. They're essentially holding each other hostage.
By marking the delegate as weak, you tell Swift: "I want to talk to this object if it exists, but I don't want to be the reason it stays alive." This is why the protocol must be constrained to AnyObject—weak references only work with classes, not structs or enums.
What if I don't want to implement every single method in the protocol?
In the early days of Swift, we had to use @objc optional, but that required us to inherit from NSObject, which feels clunky in pure Swift. The modern way to handle this is through Protocol Extensions.
I usually provide a "default implementation" in an extension. This makes the methods optional in practice; the delegate can choose to override them if they care about that event, or just ignore them if they don't. It keeps your View Controller code from being cluttered with empty functions.
extension MusicPlayerDelegate {
// Default empty implementations
func musicPlayerDidStartPlaying(_ player: MusicPlayer) {}
func musicPlayerDidPause(_ player: MusicPlayer) {}
func musicPlayerDidFinishSong(_ player: MusicPlayer, songTitle: String) {}
func musicPlayerDidEncounterError(_ player: MusicPlayer, error: Error) {}
}
Now, if your SettingsViewController only cares about errors, you only have to write the musicPlayerDidEncounterError method. The rest are handled by the extension.
How do I actually "connect" the two objects?
This is the part where learners often trip up. You have to do two things: make the class conform to the protocol, and then explicitly assign that class as the delegate.
I like to do this in viewDidLoad or the initializer. If you forget the assignment step, your code will compile perfectly, but your delegate methods will never fire because the MusicPlayer is shouting into a void.
class PlayerViewController: UIViewController, MusicPlayerDelegate {
let player = MusicPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// This is the critical line!
player.delegate = self
}
func musicPlayerDidFinishSong(_ player: MusicPlayer, songTitle: String) {
print("Updating UI: \(songTitle) has ended.")
}
}📋 Practical Task
Exercise: Building a Custom Image Downloader Notifier
Your goal is to create a system where a background downloader notifies a UI component about its progress using the Delegate pattern.
- Create a protocol called
ImageDownloaderDelegate. It should have two methods:downloaderDidUpdateProgress(_ progress: Float)anddownloaderDidComplete(imageName: String). - Create an
ImageDownloaderclass. It should have aweak var delegate. - Inside
ImageDownloader, write a method calledstartDownload(name: String). Use a loop or a timer to simulate progress (e.g., calling the progress delegate method 3 times with 0.3, 0.6, and 1.0) before finally calling the completion method. - Create a class
ImageGalleryViewthat conforms toImageDownloaderDelegate. - Implement the delegate methods in
ImageGalleryViewto print the progress and the final image name to the console. - Instantiate both classes and connect them so that starting a download in the
ImageDownloadertriggers the prints inImageGalleryView.
There are no comments for now.