[go: up one dir, main page]

0% found this document useful (0 votes)
11 views46 pages

Complete URLSession Guide in Swift

Uploaded by

sachin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views46 pages

Complete URLSession Guide in Swift

Uploaded by

sachin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 46

Complete URLSession Guide in Swift

Table of Contents
1. Introduction to URLSession
2. URLSession Architecture
3. Types of URLSession
4. URLSessionConfiguration
5. URLRequest
6. Data Tasks
7. Download Tasks
8. Upload Tasks
9. WebSocket Tasks
10. URLSessionDelegate
11. Error Handling
12. Authentication
13. Background Sessions
14. Performance Optimization
15. Modern Approaches with async/await
16. Best Practices
17. Common Patterns
18. Debugging and Testing

1. Introduction to URLSession {#introduction}


URLSession is Apple's powerful networking API introduced in iOS 7 and macOS 10.9. It provides a
comprehensive set of APIs for downloading and uploading data to and from servers using standard
Internet protocols.
Key Features
Protocol Support: HTTP/HTTPS, FTP, and custom protocols
Background Downloads: Continue downloads when app is backgrounded
Authentication: Built-in support for various authentication methods
Cookie Management: Automatic cookie handling
Cache Management: Flexible caching policies
Request/Response Processing: Comprehensive header and body handling
Why URLSession Over NSURLConnection
Better performance and memory management
Background transfer capabilities
More flexible configuration options
Better error handling and debugging
Thread-safe design

2. URLSession Architecture {#architecture}


URLSession follows a session-based architecture with the following key components:
swift
┌─────────────────┐ ┌──────────────────┐ ┌──────────
│ URLSession │
──── ──── │ URLSessionTask │ │ URLSessionDelegate
│ │ │ │ │ │
│ - Configuration │ │ - DataTask │ │ - Task Events │
│ - Task Factory │ │ - DownloadTask │ │ - Progress │
│ - Delegate │ │ - UploadTask │ │ - Authentication│
└─────────────────┘ │ - WebSocketTask │ │ - Validation │
└──────────────────┘ └─────────────────┘

Core Components
1. URLSession: The central coordination object
2. URLSessionConfiguration: Configuration for session behavior
3. URLSessionTask: Individual network requests
4. URLSessionDelegate: Handles session and task events

3. Types of URLSession {#types}


3.1 Shared Session
swift
let session = URLSession.shared
Singleton instance
Uses default configuration
No customization options
Good for simple requests
3.2 Default Session
swift
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
Uses default behavior but customizable
Stores cookies and credentials
Uses shared cache
3.3 Ephemeral Session
swift
let configuration = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: configuration)
No persistent storage (cookies, cache, credentials)
Good for private browsing
Data stored in RAM only
3.4 Background Session
swift
let configuration = URLSessionConfiguration.background(withIdentifier: "com.app.background")
let session = URLSession(configuration: configuration)
Continues transfers when app is suspended
Managed by system daemon
Requires unique identifier

4. URLSessionConfiguration {#configuration}
URLSessionConfiguration defines the behavior and policies for URLSession.
4.1 Basic Configuration Properties
swift
let configuration = URLSessionConfiguration.default
// Timeout intervals
configuration.timeoutIntervalForRequest = 30.0
configuration.timeoutIntervalForResource = 60.0
// HTTP properties
configuration.httpMaximumConnectionsPerHost = 4
configuration.httpShouldUsePipelining = false
configuration.httpShouldSetCookies = true
// Cache policy
configuration.requestCachePolicy = .useProtocolCachePolicy
configuration.urlCache = URLCache.shared
// Cookie storage
configuration.httpCookieStorage = HTTPCookieStorage.shared
configuration.httpCookieAcceptPolicy = .always
// Cellular access
configuration.allowsCellularAccess = true
configuration.allowsExpensiveNetworkAccess = true
configuration.allowsConstrainedNetworkAccess = true

4.2 Advanced Configuration


swift
// Custom headers for all requests
configuration.httpAdditionalHeaders = [
"User-Agent": "MyApp/1.0",
"Accept": "application/json"
]
// Connection requirements
configuration.waitsForConnectivity
waitsForConnectivity = true
configuration.networkServiceType = .default
// TLS settings
configuration.tlsMinimumSupportedProtocolVersion = .tlsProtocol12
configuration.tlsMaximumSupportedProtocolVersion = .tlsProtocol13
5. URLRequest {#urlrequest}
URLRequest encapsulates all the information about a network request.
5.1 Basic URLRequest
swift
// Simple GET request
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
// HTTP method
request.httpMethod = "GET"
// Headers
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer token", forHTTPHeaderField: "Authorization")
// Timeout
request.timeoutInterval = 30.0
// Cache policy
request.cachePolicy = .reloadIgnoringLocalCacheData

5.2 POST Request with Body


swift
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// JSON body
let user = ["name": "John Doe", "email": "john@example.com"]
request.httpBody = try? JSONSerialization.data(withJSONObject: user)
// Form data body
let formData = "name=John&email=john@example.com"
request.httpBody = formData.data(using: .utf8)

5.3 Multipart Form Data


swift
func createMultipartFormData(boundary: String, parameters: [String: String], fileData: Data, fileName: String) -> D
var body = Data()
// Add parameters
for (key, value) in parameters {
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
body.append("\(value)\r\n".data(using: .utf8)!)
}
// Add file
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
body.append("Content-Type: application/octet-stream\r\n\r\n".data(using: .utf8)!)
body.append(fileData)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
return body
}
let boundary = UUID().uuidString
let body = createMultipartFormData(boundary: boundary,
parameters: ["name": "John"],
fileData: imageData,
fileName: "profile.jpg")
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = body

6. Data Tasks {#data-tasks}


Data tasks are used for receiving response data in memory.
6.1 Simple Data Task
swift
let session = URLSession.shared
let url = URL(string: "https://api.example.com/users")!
let task = session.dataTask(with: url) { data, response, error in
// Handle response on background queue
if let error = error {
print("Error: \(error)")
return
}
guard let data = data else {
print("No data received")
return
}
// Process data
do {
let users = try JSONDecoder().decode([User].self, from: data)
DispatchQueue.main.async {
// Update UI
}
} catch {
print("JSON decode error: \(error)")
}
}
task.resume()

6.2 Data Task with URLRequest


swift
func performAPIRequest<T: Codable>(request: URLRequest, responseType: T.Type Type, completion: @escaping (Resu
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(NetworkError.invalidResponse))
return
}
guard 200...299 ~= httpResponse.statusCode else {
completion(.failure(NetworkError.httpError(httpResponse.statusCode)))
return
}
guard let data = data else {
completion(.failure(NetworkError.noData))
return
}
do {
let decodedData = try JSONDecoder().decode(responseType, from: data)
completion(.success(decodedData))
} catch {
completion(.failure(error))
}
}
task.resume()
}

6.3 Generic Network Manager


swift
class NetworkManager {
static let shared = NetworkManager()
private let session: URLSession
private init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
self.session = URLSession(configuration: configuration)
}
func request<T: Codable>(
url: URL,
method: HTTPMethod = .GET,
headers: [String: String]? = nil,
body: Data? = nil,
responseType: T.Type
Type
) async throws -> T {
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.httpBody = body
headers?.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard 200...299 ~= httpResponse.statusCode else {
throw NetworkError.httpError(httpResponse.statusCode)
}
return try JSONDecoder().decode(responseType, from: data)
}
}
enum HTTPMethod: String {
case GET = "GET"
case POST = "POST"
case PUT = "PUT"
case DELETE = "DELETE"
case PATCH = "PATCH"
}
enum NetworkError: Error {
case invalidResponse
case noData
case httpError(Int)
}

7. Download Tasks {#download-tasks}


Download tasks download data directly to a file on disk.
7.1 Simple Download Task
swift
let url = URL(string: "https://example.com/largefile.zip")!
let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
if let error = error {
print("Download error: \(error)")
return
}
guard let localURL = localURL else {
print("No local URL")
return
}
// Move file to permanent location
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent("downloaded_file.zip")
do {
try FileManager.default.moveItem(at: localURL, to: destinationURL)
print("File saved to: \(destinationURL)")
} catch {
print("File move error: \(error)")
}
}
task.resume()

7.2 Download with Progress Tracking


swift
class DownloadManager: NSObject, URLSessionDownloadDelegate {
private var session: URLSession!
override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
func downloadFile(from url: URL) {
let task = session.downloadTask(with: url)
task.resume()
}
// MARK: - URLSessionDownloadDelegate
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo loc
print("Download completed: \(location)")
// Move file to permanent location
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten:
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
DispatchQueue.main.async {
print("Download progress: \(progress * 100)%")
// Update UI
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("Download failed: \(error)")
}
}
}

7.3 Resumable Downloads


swift
class ResumableDownloadManager {
private var session: URLSession
private var resumeData: Data?
init() {
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration)
}
func startDownload(from url: URL) {
let task = session.downloadTask(with: url)
task.resume()
}
func pauseDownload(task: URLSessionDownloadTask) {
task.cancel { [weak self] resumeData in
self?.resumeData = resumeData
}
}
func resumeDownload() {
guard let resumeData = resumeData else { return }
let task = session.downloadTask(withResumeData: resumeData)
task.resume()
self.resumeData = nil
}
}

8. Upload Tasks {#upload-tasks}


Upload tasks send data to a server.
8.1 Upload from Data
swift
let url = URL(string: "https://api.example.com/upload")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let uploadData = try JSONEncoder().encode(userData)
let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
if let error = error {
print("Upload error: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse {
print("Upload completed with status: \(httpResponse.statusCode)")
}
}
task.resume()

8.2 Upload from File


swift
let fileURL = URL(fileURLWithPath: "/path/to/file.txt")
let url = URL(string: "https://api.example.com/upload")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.uploadTask(with: request, fromFile: fileURL) { data, response, error in
// Handle response
}
task.resume()

8.3 Upload with Progress


swift
class UploadManager: NSObject, URLSessionTaskDelegate {
private var session: URLSession!
override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
func uploadData(_ data: Data, to url: URL) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = session.uploadTask(with: request, from: data)
task.resume()
}
// MARK: - URLSessionTaskDelegate
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesS
let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
DispatchQueue.main.async {
print("Upload progress: \(progress * 100)%")
}
}
}

9. WebSocket Tasks {#websocket-tasks}


WebSocket tasks provide real-time bidirectional communication.
9.1 Basic WebSocket Connection
swift
class WebSocketManager: NSObject, URLSessionWebSocketDelegate {
private var webSocketTask: URLSessionWebSocketTask?
private var session: URLSession!
override init() {
super.init()
session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
}
func connect(to url: URL) {
webSocketTask = session.webSocketTask
webSocketTask(with: url)
webSocketTask?.resume()
receiveMessage()
}
func disconnect() {
webSocketTask?.cancel(with: .goingAway, reason: nil)
}
func sendMessage(_ message: String) {
let message = URLSessionWebSocketTask.Message.string(message)
webSocketTask?.send(message) { error in
if let error = error {
print("Send error: \(error)")
}
}
}
private func receiveMessage() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
switch message {
case .string(let text):
print("Received text: \(text)")
case .data(let data):
print("Received data: \(data)")
@unknown default:
break
}
self?.receiveMessage() // Continue receiving
case .failure(let error):
print("Receive error: \(error)")
}
}
}
// MARK: - URLSessionWebSocketDelegate
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol pro
print("WebSocket connected")
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode:
print("WebSocket disconnected")
}
}

9.2 WebSocket with Ping/Pong


swift
extension WebSocketManager {
func startPingPong() {
webSocketTask?.sendPing { error in
if let error = error {
print("Ping error: \(error)")
} else {
print("Ping sent successfully")
}
}
}
}

10. URLSessionDelegate {#delegates}


Delegates provide fine-grained control over session and task behavior.
10.1 URLSessionDelegate
swift
extension NetworkManager: URLSessionDelegate {
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
print("Session became invalid: \(String(describing: error))")
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler
// Handle authentication challenges
completionHandler(.performDefaultHandling, nil)
}
}

10.2 URLSessionTaskDelegate
swift
extension NetworkManager: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("Task completed with error: \(error)")
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPUR
// Handle redirects
completionHandler(request)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallen
// Handle authentication
completionHandler(.performDefaultHandling, nil)
}
}

10.3 URLSessionDataDelegate
swift
extension NetworkManager: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, c
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
// Process received data incrementally
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse cachedResponse:
completionHandler(cachedResponse)
}
}

11. Error Handling {#error-handling}


Comprehensive error handling is crucial for robust networking.
11.1 Custom Error Types
swift
enum NetworkError: Error, LocalizedError {
case invalidURL
case noData
case decodingError
case serverError(Int)
case timeout
case noInternetConnection
case cancelled
var errorDescription: String? {
switch self {
case .invalidURL:
return "Invalid URL"
case .noData:
return "No data received"
case .decodingError:
return "Failed to decode response"
case .serverError(let code):
return "Server error with code: \(code)"
case .timeout:
return "Request timed out"
case .noInternetConnection:
return "No internet connection"
case .cancelled:
return "Request was cancelled"
}
}
}

11.2 Error Handling Strategy


swift
extension NetworkManager {
private func handleError(_ error: Error) -> NetworkError {
if let urlError = error as? URLError {
switch urlError.code {
case .notConnectedToInternet, .networkConnectionLost:
return .noInternetConnection
case .timedOut:
return .timeout
case .cancelled:
return .cancelled
default:
return .serverError(urlError.errorCode)
}
}
return .serverError(0)
}
private func validateResponse(_ response: URLResponse?) throws -> HTTPURLResponse {
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.serverError(0)
}
guard 200...299 ~= httpResponse.statusCode else {
throw NetworkError.serverError(httpResponse.statusCode)
}
return httpResponse
}
}

11.3 Retry Mechanism


swift
class RetryableNetworkManager {
private let maxRetries = 3
private let retryDelay: TimeInterval = 1.0
func performRequestWithRetry<T: Codable>(
request: URLRequest,
responseType: T.TypeType,
retryCount: Int = 0
) async throws -> T {
do {
let (data, response) = try await URLSession.shared.data(for: request)
let httpResponse = try validateResponse(response)
return try JSONDecoder().decode(responseType, from: data)
} catch {
if retryCount < maxRetries && shouldRetry(error) {
try await Task.sleep(nanoseconds: UInt64(retryDelay * 1_000_000_000))
return try await performRequestWithRetry(
request: request,
responseType: responseType,
retryCount: retryCount + 1
)
}
throw error
}
}
private func shouldRetry(_ error: Error) -> Bool {
if let urlError = error as? URLError {
switch urlError.code {
case .timedOut, .networkConnectionLost, .notConnectedToInternet:
return true
default:
return false
}
}
return false
}
}

12. Authentication {#authentication}


URLSession supports various authentication methods.
12.1 Basic Authentication
swift
func addBasicAuthentication(to request: inout URLRequest, username: String, password: String) {
let credentials = "\(username):\(password)"
let base64Credentials = Data(credentials.utf8).base64EncodedString()
request.setValue("Basic \(base64Credentials)", forHTTPHeaderField: "Authorization")
}

12.2 Bearer Token Authentication


swift
func addBearerToken(to request: inout URLRequest, token: String) {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}

12.3 Custom Authentication Challenge


swift
class AuthenticatedNetworkManager: NSObject, URLSessionDelegate {
private var credentials: URLCredential?
func setCredentials(username: String, password: String) {
credentials = URLCredential(user: username, password: password, persistence: .forSession)
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic else {
completionHandler(.performDefaultHandling, nil)
return
}
if challenge.previousFailureCount == 0 {
completionHandler(.useCredential, credentials)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
12.4 OAuth 2.0 Flow
swift
class OAuth2Manager {
private let clientID: String
private let redirectURI: String
private var accessToken: String?
init(clientID: String, redirectURI: String) {
self.clientID = clientID
self.redirectURI = redirectURI
}
func exchangeCodeForToken(code: String) async throws -> String {
let url = URL(string: "https://oauth.provider.com/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let body = "grant_type=authorization_code&code=\(code)&client_id=\(clientID)&redirect_uri=\(redirectURI)"
request.httpBody = body.data(using: .utf8)
let (data, _) = try await URLSession.shared.data(for: request)
let tokenResponse = try JSONDecoder().decode(TokenResponse.self, from: data)
accessToken = tokenResponse.accessToken
return tokenResponse.accessToken
}
}
struct TokenResponse: Codable {
let accessToken: String
let tokenType: String
let expiresIn: Int
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
}
}
13. Background Sessions {#background-sessions}
Background sessions allow transfers to continue when the app is not running.
13.1 Setting Up Background Session
swift
class BackgroundTransferManager: NSObject, URLSessionDownloadDelegate {
static let shared = BackgroundTransferManager()
private var backgroundSession: URLSession!
override init() {
super.init()
let config = URLSessionConfiguration.background(withIdentifier: "com.app.background-transfer")
config.sessionSendsLaunchEvents = true
backgroundSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
func downloadInBackground(url: URL) {
let task = backgroundSession.downloadTask(with: url)
task.resume()
}
// MARK: - URLSessionDownloadDelegate
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo loc
// Handle completed download
DispatchQueue.main.async {
self.handleDownloadCompletion(location: location)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten:
// Update progress
}
private func handleDownloadCompletion(location: URL) {
// Process downloaded file
// Send local notification if needed
}
}

13.2 App Delegate Integration


swift
// In AppDelegate or SceneDelegate
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completio
// Store completion handler
BackgroundTransferManager.shared.backgroundCompletionHandler = completionHandler
}
// In BackgroundTransferManager
var backgroundCompletionHandler: (() -> Void)?
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.backgroundCompletionHandler?()
self.backgroundCompletionHandler = nil
}
}

14. Performance Optimization {#performance}


14.1 Connection Pooling
swift
let configuration = URLSessionConfiguration.default
configuration.httpMaximumConnectionsPerHost = 4
configuration.requestCachePolicy = .useProtocolCachePolicy

14.2 Request Batching


swift
class BatchRequestManager {
func performBatchRequests<T: Codable>(
requests: [URLRequest],
responseType: T.Type
Type
) async -> [Result<T, Error>] {
await withTaskGroup(of: Result<T, Error>.self) { group in
for request in requests {
group.addTask {
do {
let (data, _) = try await URLSession.shared.data(for: request)
let result = try JSONDecoder().decode(responseType, from: data)
return .success(result)
} catch {
return .failure(error)
}
}
}
var results: [Result<T, Error>] = []
for await result in group {
results.append(result)
}
return results
}
}
}

14.3 Caching Strategy


swift
class CacheManager {
private let cache = URLCache(memoryCapacity: 50 * 1024 * 1024, // 50MB
diskCapacity: 100 * 1024 * 1024, // 100MB
diskPath: "URLCache")
func configureCaching() {
URLCache.shared = cache
let configuration = URLSessionConfiguration.default
configuration.urlCache = cache
configuration.requestCachePolicy = .returnCacheDataElseLoad
}
func clearCache() {
cache.removeAllCachedResponses()
}
func cacheResponse(for request: URLRequest, data: Data, response: URLResponse) {
let cachedResponse = CachedURLResponse(response: response, data: data)
cache.storeCachedResponse(cachedResponse, for: request)
}
}

14.4 HTTP/2 and HTTP/3 Support


swift
let configuration = URLSessionConfiguration.default
configuration.httpShouldUsePipelining = true
configuration.httpMaximumConnectionsPerHost = 1 // HTTP/2 uses multiplexing
// HTTP/3 is automatically used when available
configuration.allowsCellularAccess = true
configuration.allowsExpensiveNetworkAccess = true

15. Modern Approaches with async/await {#async-await}


15.1 Basic async/await Usage
swift
class ModernNetworkManager {
private let session: URLSession
init() {
self.session = URLSession.shared
}
func fetchData<T: Codable>(from url: URL, responseType: T.Type
Type) async throws -> T {
let (data, response) = try await session.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
200...299 ~= httpResponse.statusCode else {
throw NetworkError.serverError((response as? HTTPURLResponse)?.statusCode ?? 0)
}
return try JSONDecoder().decode(responseType, from: data)
}
func uploadData<T: Codable>(
to url: URL,
data: Data,
responseType: T.Type
Type
) async throws -> T {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let (responseData, response) = try await session.upload(for: request, from: data)
guard let httpResponse = response as? HTTPURLResponse,
200...299 ~= httpResponse.statusCode else {
throw NetworkError.serverError((response as? HTTPURLResponse)?.statusCode ?? 0)
}
return try JSONDecoder().decode(responseType, from: responseData)
}
}

15.2 AsyncSequence for Streaming


swift
extension URLSession {
func bytes(from url: URL) -> URLSession.AsyncBytes {
return bytes(from: URLRequest(url: url))
}
}
func processStreamingData(from url: URL) async throws {
let (asyncBytes, response) = try await URLSession.shared.bytes(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.serverError(0)
}
for try await byte in asyncBytes {
// Process each byte as it arrives
processStreamingByte(byte)
}
}

15.3 Concurrent Downloads


swift
func downloadMultipleFiles(urls: [URL]) async throws -> [Data] {
try await withThrowingTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
var results: [Data] = []
for try await data in group {
results.append(data)
}
return results
}
}

15.4 Async Download with Progress


swift
class AsyncDownloadManager: NSObject, URLSessionDownloadDelegate {
private var progressContinuation: AsyncStream<Double>.Continuation?
private var completionContinuation: CheckedContinuation<URL, Error>?
func downloadWithProgress(from url: URL) -> (progress: AsyncStream<Double>, completion: Task<URL, Error>
let (progressStream, progressContinuation) = AsyncStream.makeStream(of: Double.self)
self.progressContinuation = progressContinuation
let completion = Task<URL, Error> {
try await withCheckedThrowingContinuation { continuation in
self.completionContinuation = continuation
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: url)
task.resume()
}
}
return (progressStream, completion)
}
// MARK: - URLSessionDownloadDelegate
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo loc
completionContinuation?.resume(returning: location)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten:
let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
progressContinuation?.yield
yield(progress)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
completionContinuation?.resume(throwing: error)
}
progressContinuation?.finish()
}
}
16. Best Practices {#best-practices}
16.1 General Guidelines
1. Always handle errors gracefully
2. Use appropriate timeout values
3. Implement proper retry logic
4. Cache responses when appropriate
5. Use background sessions for large transfers
6. Validate SSL certificates in production
7. Monitor network reachability
8. Use proper HTTP status code handling
16.2 Security Best Practices
swift
class SecureNetworkManager {
private let session: URLSession
init() {
let configuration = URLSessionConfiguration.default
// Security configurations
configuration.tlsMinimumSupportedProtocolVersion = .tlsProtocol12
configuration.httpShouldSetCookies = false // For APIs
configuration.httpCookieAcceptPolicy = .never // For APIs
self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
}
extension SecureNetworkManager: URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler
// Certificate pinning
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Validate against pinned certificate
if validateServerTrust(serverTrust) {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
private func validateServerTrust(_ serverTrust: SecTrust) -> Bool {
// Implement certificate pinning validation
return true // Simplified for example
}
}

16.3 Memory Management


swift
class MemoryEfficientNetworkManager {
private weak var currentTask: URLSessionTask?
func performRequest() {
// Cancel previous request if still running
currentTask?.cancel()
let task = URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { [weak self] data, respo
// Use weak self to prevent retain cycles
self?.handleResponse(data: data, response: response, error: error)
}
currentTask = task
task.resume()
}
private func handleResponse(data: Data?, response: URLResponse?, error: Error?) {
// Process response
currentTask = nil // Clear reference
}
deinit {
currentTask?.cancel()
}
}

16.4 Testing Strategies


swift
protocol NetworkSessionProtocol {
func data(for request: URLRequest) async throws -> (Data, URLResponse)
func data(from url: URL) async throws -> (Data, URLResponse)
}
extension URLSession: NetworkSessionProtocol {}
class TestableNetworkManager {
private let session: NetworkSessionProtocol
init(session: NetworkSessionProtocol = URLSession.shared) {
self.session = session
}
func fetchData<T: Codable>(from url: URL, responseType: T.Type
Type) async throws -> T {
let (data, _) = try await session.data(from: url)
return try JSONDecoder().decode(responseType, from: data)
}
}
// Mock for testing
class MockURLSession: NetworkSessionProtocol {
var mockData: Data?
var mockResponse: URLResponse?
var mockError: Error?
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
if let error = mockError {
throw error
}
return (mockData ?? Data(), mockResponse ?? URLResponse())
}
func data(from url: URL) async throws -> (Data, URLResponse) {
return try await data(for: URLRequest(url: url))
}
}

17. Common Patterns {#common-patterns}


17.1 Repository Pattern
swift
protocol UserRepositoryProtocol {
func fetchUsers() async throws -> [User]
func fetchUser(id: String) async throws -> User
func createUser(_ user: User) async throws -> User
func updateUser(_ user: User) async throws -> User
func deleteUser(id: String) async throws
}
class UserRepository: UserRepositoryProtocol {
private let networkManager: NetworkManager
private let baseURL = "https://api.example.com/users"
init(networkManager: NetworkManager = .shared) {
self.networkManager = networkManager
}
func fetchUsers() async throws -> [User] {
let url = URL(string: baseURL)!
return try await networkManager.request(url: url, responseType: [User].self)
}
func fetchUser(id: String) async throws -> User {
let url = URL(string: "\(baseURL)/\(id)")!
return try await networkManager.request(url: url, responseType: User.self)
}
func createUser(_ user: User) async throws -> User {
let url = URL(string: baseURL)!
let userData = try JSONEncoder().encode(user)
return try await networkManager.request(
url: url,
method: .POST,
headers: ["Content-Type": "application/json"],
body: userData,
responseType: User.self
)
}
func updateUser(_ user: User) async throws -> User {
let url = URL(string: "\(baseURL)/\(user.id)")!
let userData = try JSONEncoder().encode(user)
return try await networkManager.request(
url: url,
method: .PUT,
headers: ["Content-Type": "application/json"],
body: userData,
responseType: User.self
)
}
func deleteUser(id: String) async throws {
let url = URL(string: "\(baseURL)/\(id)")!
let _: EmptyResponse = try await networkManager.request(
url: url,
method: .DELETE,
responseType: EmptyResponse.self
)
}
}
struct EmptyResponse: Codable {}

17.2 Request Builder Pattern


swift
class RequestBuilder {
private var request: URLRequest
init(url: URL) {
self.request = URLRequest(url: url)
}
func method(_ method: HTTPMethod) -> Self {
request.httpMethod = method.rawValue
return self
}
func header(_ key: String, _ value: String) -> Self {
request.setValue(value, forHTTPHeaderField: key)
return self
}
func headers(_ headers: [String: String]) -> Self {
headers.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
return self
}
func body(_ data: Data) -> Self {
request.httpBody = data
return self
}
func jsonBody<T: Encodable>(_ object: T) throws -> Self {
let data = try JSONEncoder().encode(object)
request.httpBody = data
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return self
}
func timeout(_ interval: TimeInterval) -> Self {
request.timeoutInterval = interval
return self
}
func build() -> URLRequest {
return request
}
}
// Usage
let request = try RequestBuilder(url: URL(string: "https://api.example.com/users")!)
.method(.POST)
.header("Authorization", "Bearer token")
.jsonBody(user)
.timeout(30)
.build()

17.3 Response Interceptor Pattern


swift
protocol ResponseInterceptor {
func intercept(data: Data, response: URLResponse) throws -> Data
}
class AuthenticationInterceptor: ResponseInterceptor {
func intercept(data: Data, response: URLResponse) throws -> Data {
guard let httpResponse = response as? HTTPURLResponse else {
return data
}
if httpResponse.statusCode == 401 {
// Handle token refresh
throw NetworkError.authenticationRequired
}
return data
}
}
class LoggingInterceptor: ResponseInterceptor {
func intercept(data: Data, response: URLResponse) throws -> Data {
if let httpResponse = response as? HTTPURLResponse {
print("Response: \(httpResponse.statusCode)")
}
return data
}
}
class InterceptorNetworkManager {
private let interceptors: [ResponseInterceptor]
init(interceptors: [ResponseInterceptor] = []) {
self.interceptors = interceptors
}
func request<T: Codable>(url: URL, responseType: T.Type
Type) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
var processedData = data
for interceptor in interceptors {
processedData = try interceptor.intercept(data: processedData, response: response)
}
return try JSONDecoder().decode(responseType, from: processedData)
}
}

18. Debugging and Testing {#debugging}


18.1 Network Debugging
swift
class DebuggingNetworkManager {
private let session: URLSession
init() {
let configuration = URLSessionConfiguration.default
// Enable debug logging in Debug builds
#if DEBUG
configuration.protocolClasses = [DebuggingURLProtocol.self]
#endif
self.session = URLSession(configuration: configuration)
}
}
class DebuggingURLProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
// Log request
print("🚀 Request: \(request.httpMethod ?? "GET") \(request.url?.absoluteString ?? "")")
if let headers = request.allHTTPHeaderFields {
print("📋 Headers: \(headers)")
}
if let body = request.httpBody {
print("📦 Body: \(String(data: body, encoding: .utf8) ?? "Binary data")")
}
// Continue with actual request
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
if let error = error {
print("❌ Error: \(error)")
self?.client?.urlProtocol(self!, didFailWithError: error)
return
}
if let httpResponse = response as? HTTPURLResponse {
print("✅ Response: \(httpResponse.statusCode)")
}
if let data = data {
print("📥 Response Data: \(String(data: data, encoding: .utf8) ?? "Binary data")")
self?.client?.urlProtocol(self!, didLoad: data)
}
if let response = response {
self?.client?.urlProtocol(self!, didReceive: response, cacheStoragePolicy: .notAllowed)
}
self?.client?.urlProtocolDidFinishLoading(self!)
}
task.resume()
}
override func stopLoading() {
// Clean up if needed
}
}

18.2 Unit Testing Network Code


swift
import XCTest
class NetworkManagerTests: XCTestCase {
var networkManager: TestableNetworkManager!
var mockSession: MockURLSession!
override func setUp() {
super.setUp()
mockSession = MockURLSession()
networkManager = TestableNetworkManager(session: mockSession)
}
func testFetchUsersSuccess() async throws {
// Given
let users = [User(id: "1", name: "John")]
let userData = try JSONEncoder().encode(users)
mockSession.mockData = userData
mockSession.mockResponse = HTTPURLResponse(
url: URL(string: "https://api.example.com")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)
// When
let result = try await networkManager.fetchData(
from: URL(string: "https://api.example.com")!,
responseType: [User].self
)
// Then
XCTAssertEqual(result.count, 1)
XCTAssertEqual(result.first?.name, "John")
}
func testFetchUsersNetworkError() async {
// Given
mockSession.mockError = URLError(.notConnectedToInternet)
// When/Then
do {
_ = try await networkManager.fetchData(
from: URL(string: "https://api.example.com")!,
responseType: [User].self
)
XCTFail("Should have thrown an error")
} catch {
XCTAssertTrue(error is URLError)
}
}
}

18.3 Integration Testing


swift
class NetworkIntegrationTests: XCTestCase {
func testRealAPICall() async throws {
let expectation = XCTestExpectation(description: "API call completes")
let url = URL(string: "https://httpbin.org/get")!
do {
let (data, response) = try await URLSession.shared.data(from: url)
XCTAssertNotNil(data)
XCTAssertTrue(response is HTTPURLResponse)
if let httpResponse = response as? HTTPURLResponse {
XCTAssertEqual(httpResponse.statusCode, 200)
}
expectation.fulfill()
} catch {
XCTFail("Network request failed: \(error)")
}
await fulfillment(of: [expectation], timeout: 10.0)
}
}

18.4 Performance Testing


swift
class NetworkPerformanceTests: XCTestCase {
func testNetworkRequestPerformance() {
let url = URL(string: "https://httpbin.org/get")!
measure {
let expectation = XCTestExpectation(description: "Request completes")
URLSession.shared.dataTask(with: url) { _, _, _ in
expectation.fulfill()
}.resume()
wait(for: [expectation], timeout: 10.0)
}
}
}

Conclusion
URLSession is a powerful and flexible networking framework that provides everything needed for
modern iOS and macOS applications. From simple data requests to complex background transfers,
WebSocket connections, and advanced authentication flows, URLSession handles it all with efficiency
and reliability.
Key takeaways:
Use the appropriate session type for your use case
Implement proper error handling and retry logic
Leverage async/await for cleaner code
Follow security best practices
Test your networking code thoroughly
Monitor performance and optimize as needed
This guide covers the essential concepts and patterns you'll need to build robust networking solutions
in your Swift applications. As you implement these patterns, remember to adapt them to your specific
requirements and always consider the user experience when dealing with network operations.

Additional Resources
Apple's URLSession Documentation
WWDC Videos on Networking
Swift API Design Guidelines
iOS App Transport Security

This document provides a comprehensive overview of URLSession in Swift. For the most up-to-date
information, always refer to Apple's official documentation and WWDC sessions.

You might also like