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
82: URLSessionDelegate for Advanced Control
I remember a developer I mentored a few years back who was building a high-res photo backup tool. He had everything working perfectly with the standard dataTask(with:completionHandler:) approach, but there was one glaring problem: for a 20MB upload, the UI just... sat there. The user had no idea if the app had crashed or if the upload was actually happening. He kept trying to find a "progress" parameter in the completion handler, not realizing that by the time that closure fires, the work is already done. He was trying to track a journey by only looking at the destination.
When you need to know what's happening while a request is in flight—whether that's updating a progress bar, handling a specific authentication challenge, or managing a background download—the completion handler is too blunt an instrument. This is where URLSessionDelegate comes in. Instead of a single callback at the end, the delegate pattern turns the network request into a conversation. The session calls you back at specific milestones throughout the request's lifecycle.
Hooking into the Request Lifecycle
To move beyond the basic completion handler, you have to initialize your URLSession with a delegate. Most of the time, you'll create a custom class that inherits from NSObject and adopts one of the specialized delegate protocols, like URLSessionDataDelegate or URLSessionTaskDelegate. I usually recommend adopting URLSessionTaskDelegate first because it handles the "big picture" events, like when a task finishes or fails.
class NetworkManager: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate {
lazy var session: URLSession = {
// We pass 'self' as the delegate here
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}()
func startUpload(url: URL, fileData: Data) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = session.uploadTask(with: request, from: fileData)
task.resume()
}
}
Notice that I used delegateQueue: .main. Since you'll likely be updating the UI with the progress you're about to track, specifying the main queue here saves you from wrapping every single delegate call in a DispatchQueue.main.async block. It's a small quality-of-life win that keeps the code much cleaner.
Tracking Byte-by-Byte Progress
If you want to build that progress bar my mentee was struggling with, you need URLSessionTaskDelegate. Specifically, the urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:) method. This method is called repeatedly as the system pushes chunks of data to the server. It's the heartbeat of your upload.
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
print("Upload Progress: \(String(format: "%.2f", progress * 100))%")
// Here is where you'd update your UI progress bar
}
One thing to watch out for: totalBytesExpectedToSend can sometimes be -1 if the server doesn't know the size of the payload upfront. Always check for that, or your app will crash trying to divide by zero. It's one of those "gotchas" that doesn't show up in the documentation but will definitely show up in your crash logs.
Intercepting Authentication Challenges
Beyond progress bars, the delegate is your only real way to handle "Advanced Control" scenarios like SSL pinning or custom server trust. When a server presents a certificate that the OS doesn't automatically trust, URLSession doesn't just fail; it asks the delegate: "Hey, do you trust this?"
You do this by implementing urlSession(_:didReceive:completionHandler:). This is where you can inspect the challenge object. If you're implementing a high-security app, you can compare the server's public key against a known copy bundled in your app. If they match, you call the completion handler with .useCredential; if they don't, you cancel the request immediately to prevent a man-in-the-middle attack.
It feels a bit verbose compared to a simple closure, but that's the trade-off. You're exchanging brevity for total visibility into the network stack.
📋 Practical Task
Build a Real-Time File Upload Monitor
Create a class called UploadMonitor that manages a file upload and reports progress to the console. Your implementation must meet the following requirements:
- Adopt
NSObject,URLSessionTaskDelegate, andURLSessionDataDelegate. - Initialize a
URLSessionwithselfas the delegate and use the main queue. - Implement
urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)to calculate the percentage of the upload completed. - The progress should be printed to the console as a percentage (e.g., "Upload: 45.2%").
- Implement a method
uploadLargeFile(url: URL, data: Data)that starts auploadTask. - Add a safeguard to handle cases where
totalBytesExpectedToSendis less than or equal to zero to avoid division errors.
There are no comments for now.