The Ultimate Guide to API Calling in Swift 5: From TCP/UDP to Advanced Networking
The Ultimate Guide to API Calling in Swift 5: From TCP/UDP to Advanced Networking
API calling is the backbone of modern iOS applications. Whether you're building a social media app, e-commerce platform, or data-driven application, understanding how to effectively communicate with remote servers is crucial. This comprehensive guide covers everything from networking fundamentals to advanced API implementation techniques in Swift 5.
Table of Contents
- Networking Fundamentals
- TCP vs UDP
- HTTP Methods Deep Dive
- URLSession Architecture
- GET Requests
- POST Requests
- PUT Requests
- DELETE Requests
- Advanced Networking
- Error Handling
- Authentication
- Performance Optimisation
- Testing API Calls
- Best Practices
Networking Fundamentals
Understanding the Network Stack
Before diving into Swift-specific implementations, it's essential to understand the underlying network protocols that power API communication.
The OSI Model in Context
The Open Systems Interconnection (OSI) model provides a conceptual framework for understanding network communication:
- Physical Layer: Hardware transmission (cables, wireless)
- Data Link Layer: Node-to-node delivery (Ethernet, Wi-Fi)
- Network Layer: Routing (IP)
- Transport Layer: End-to-end communication (TCP, UDP)
- Session Layer: Managing connections
- Presentation Layer: Data formatting (encryption, compression)
- Application Layer: User interface (HTTP, HTTPS, FTP)
For iOS developers, we primarily work with layers 4-7, with HTTP/HTTPS at the application layer being our main focus.
Internet Protocol Suite
The Internet Protocol Suite (TCP/IP) is the foundation of internet communication:
- Internet Protocol (IP): Delivers packets from source to destination
- Transmission Control Protocol (TCP): Reliable, connection-oriented protocol
- User Datagram Protocol (UDP): Fast, connectionless protocol
- Internet Control Message Protocol (ICMP): Error reporting and diagnostics
TCP vs UDP
Understanding the differences between TCP and UDP is crucial for choosing the right protocol for your application needs.
TCP (Transmission Control Protocol)
TCP is a connection-oriented, reliable protocol that ensures data integrity and ordered delivery.
TCP Characteristics:
- Connection-Oriented: Establishes a connection before data transfer
- Reliable: Guarantees data delivery and order
- Error Detection: Built-in error checking and correction
- Flow Control: Manages data transmission rate
- Congestion Control: Adapts to network conditions
When to Use TCP:
- Web browsing (HTTP/HTTPS)
- File transfers (FTP)
- Email (SMTP, POP3, IMAP)
- Remote login (SSH, Telnet)
- Most REST APIs
TCP Connection Process (Three-Way Handshake):
- SYN: Client sends synchronization request
- SYN-ACK: Server acknowledges and sends synchronization
- ACK: Client acknowledges server's synchronization
UDP (User Datagram Protocol)
UDP is a connectionless, fast protocol that prioritizes speed over reliability.
UDP Characteristics:
- Connectionless: No connection establishment required
- Fast: Lower overhead, faster transmission
- Unreliable: No guarantee of delivery or order
- No Flow Control: Sends data at maximum rate
- Stateless: Each packet is independent
When to Use UDP:
- Real-time gaming
- Video streaming
- DNS lookups
- Live broadcasts
- IoT sensor data
- Voice over IP (VoIP)
Comparison Table
Feature | TCP | UDP |
---|---|---|
Connection | Connection-oriented | Connectionless |
Reliability | Reliable | Unreliable |
Speed | Slower | Faster |
Overhead | High | Low |
Error Checking | Yes | Basic |
Use Cases | Web APIs, File Transfer | Gaming, Streaming |
HTTP Methods Deep Dive
HTTP (HyperText Transfer Protocol) defines several methods for different types of operations. Understanding each method's purpose and proper usage is essential for RESTful API design.
HTTP Method Categories
Safe Methods
Methods that don't modify server state:
- GET: Retrieve data
- HEAD: Retrieve headers only
- OPTIONS: Get allowed methods
Idempotent Methods
Methods that produce the same result regardless of how many times they're executed:
- GET, PUT, DELETE, HEAD, OPTIONS
Non-Idempotent Methods
Methods that may produce different results on repeated calls:
- POST
Detailed HTTP Methods
GET - Retrieve Data
- Purpose: Retrieve data from server
- Safe: Yes (read-only)
- Idempotent: Yes
- Cacheable: Yes
- Body: No request body
- Response: Contains requested data
POST - Create New Resources
- Purpose: Submit data to create new resources
- Safe: No (modifies server state)
- Idempotent: No
- Cacheable: No
- Body: Contains data to be processed
- Response: Usually contains created resource
PUT - Update/Replace Resources
- Purpose: Update entire resource or create if doesn't exist
- Safe: No (modifies server state)
- Idempotent: Yes
- Cacheable: No
- Body: Contains complete resource data
- Response: Updated resource or confirmation
DELETE - Remove Resources
- Purpose: Remove specified resource
- Safe: No (modifies server state)
- Idempotent: Yes
- Cacheable: No
- Body: Usually no body
- Response: Confirmation or deleted resource
PATCH - Partial Updates
- Purpose: Apply partial modifications
- Safe: No
- Idempotent: No (generally)
- Cacheable: No
- Body: Contains partial update data
- Response: Updated resource
HEAD - Retrieve Headers
- Purpose: Get response headers without body
- Safe: Yes
- Idempotent: Yes
- Cacheable: Yes
- Body: No request body
- Response: Headers only, no body
OPTIONS - Discover Capabilities
- Purpose: Get allowed methods and capabilities
- Safe: Yes
- Idempotent: Yes
- Cacheable: Yes
- Body: Usually no body
- Response: Allowed methods and options
URLSession Architecture
URLSession is Apple's powerful networking API that provides a rich set of delegate methods for handling uploads, downloads, and data tasks in the background.
URLSession Components
URLSession
The main class that manages network requests. Different session types:
- Shared Session:
URLSession.shared
- Simple requests - Default Session: Custom configuration, similar to shared
- Ephemeral Session: No persistent storage (private browsing)
- Background Session: Background downloads/uploads
URLSessionConfiguration
Configures session behavior:
- Timeout intervals
- Cache policies
- Cookie handling
- Connection requirements
- Protocol support
URLSessionTask Types
- URLSessionDataTask: In-memory requests/responses
- URLSessionUploadTask: Upload files to servers
- URLSessionDownloadTask: Download files to disk
- URLSessionStreamTask: TCP/IP connections
- URLSessionWebSocketTask: WebSocket connections (iOS 13+)
URLRequest
Represents a URL load request with:
- URL: Target endpoint
- HTTP Method: GET, POST, etc.
- Headers: Request metadata
- Body: Request payload
- Cache Policy: Caching behavior
- Timeout: Request timeout
Session Lifecycle
- Create URLSession with appropriate configuration
- Create URLRequest with endpoint details
- Create URLSessionTask from session and request
- Start task using
resume()
- Handle response in completion handler or delegate
- Process data and update UI
- Clean up resources
Thanks for reading this long,
When building advanced API calling for video call or livestream in Swift 5, always use WebRTC for real-time peer-to-peer communication combined with URLSessionWebSocketTask or a dedicated signalling server (via REST + WebSockets) to exchange session descriptions and ICE candidates; implement adaptive bitrate streaming (HLS or RTMP fallback) to handle varying network conditions; use background tasks and QoS priorities to keep streaming alive without blocking UI; secure API calls with JWT or OAuth 2.0 tokens to protect streams; add retry logic with exponential backoff for signalling and media endpoints; and profile with Instruments (Network + Energy) to ensure low latency and battery efficiency—this combination ensures stable, secure, and scalable real-time video communication.
GET Requests
GET requests are the most common HTTP method, used to retrieve data from servers. They should be safe (read-only) and idempotent.
Basic GET Implementation
import Foundation
class APIManager {
static let shared = APIManager()
private let session = URLSession.shared
private let baseURL = "https://api.example.com"
private init() {}
func fetchUsers(completion: @escaping (Result<[User], APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(.networkError(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.invalidResponse))
return
}
guard 200...299 ~= httpResponse.statusCode else {
completion(.failure(.serverError(httpResponse.statusCode)))
return
}
guard let data = data else {
completion(.failure(.noData))
return
}
do {
let users = try JSONDecoder().decode([User].self, from: data)
completion(.success(users))
} catch {
completion(.failure(.decodingError(error)))
}
}
}
task.resume()
}
}
Advanced GET with Query Parameters
extension APIManager {
func fetchUsers(page: Int, limit: Int, search: String?, completion: @escaping (Result<UserResponse, APIError>) -> Void) {
var components = URLComponents(string: "\(baseURL)/users")!
var queryItems = [
URLQueryItem(name: "page", value: String(page)),
URLQueryItem(name: "limit", value: String(limit))
]
if let search = search, !search.isEmpty {
queryItems.append(URLQueryItem(name: "search", value: search))
}
components.queryItems = queryItems
guard let url = components.url else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
performDataTask(request: request, completion: completion)
}
}
GET with Custom Headers
extension APIManager {
func fetchUserProfile(userID: String, completion: @escaping (Result<UserProfile, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(userID)") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
// Custom headers
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
request.setValue("iOS", forHTTPHeaderField: "X-Client-Platform")
request.setValue(Bundle.main.appVersion, forHTTPHeaderField: "X-Client-Version")
request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-ID")
// Cache policy
request.cachePolicy = .returnCacheDataElseLoad
request.timeoutInterval = 30
performDataTask(request: request, completion: completion)
}
}
POST Requests
POST requests are used to submit data to servers, typically to create new resources. They are neither safe nor idempotent.
Basic POST Implementation
extension APIManager {
func createUser(_ user: CreateUserRequest, completion: @escaping (Result<User, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
do {
let jsonData = try JSONEncoder().encode(user)
request.httpBody = jsonData
} catch {
completion(.failure(.encodingError(error)))
return
}
performDataTask(request: request, completion: completion)
}
}
POST with Form Data
extension APIManager {
func uploadUserAvatar(userID: String, image: UIImage, completion: @escaping (Result<UploadResponse, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(userID)/avatar") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = UUID().uuidString
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
// Create multipart form data
var formData = Data()
// Add image data
if let imageData = image.jpegData(compressionQuality: 0.8) {
formData.append("--\(boundary)\r\n".data(using: .utf8)!)
formData.append("Content-Disposition: form-data; name=\"avatar\"; filename=\"avatar.jpg\"\r\n".data(using: .utf8)!)
formData.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
formData.append(imageData)
formData.append("\r\n".data(using: .utf8)!)
}
// Add user ID
formData.append("--\(boundary)\r\n".data(using: .utf8)!)
formData.append("Content-Disposition: form-data; name=\"user_id\"\r\n\r\n".data(using: .utf8)!)
formData.append(userID.data(using: .utf8)!)
formData.append("\r\n".data(using: .utf8)!)
// Close boundary
formData.append("--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = formData
performDataTask(request: request, completion: completion)
}
}
POST with URL Encoded Data
extension APIManager {
func loginUser(email: String, password: String, completion: @escaping (Result<AuthResponse, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/auth/login") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
// Create URL encoded body
var components = URLComponents()
components.queryItems = [
URLQueryItem(name: "email", value: email),
URLQueryItem(name: "password", value: password),
URLQueryItem(name: "grant_type", value: "password"),
URLQueryItem(name: "client_id", value: "ios_app")
]
request.httpBody = components.query?.data(using: .utf8)
performDataTask(request: request, completion: completion)
}
}
PUT Requests
PUT requests are used to update entire resources or create resources if they don't exist. They should be idempotent.
Basic PUT Implementation
extension APIManager {
func updateUser(_ user: User, completion: @escaping (Result<User, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(user.id)") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
do {
let jsonData = try JSONEncoder().encode(user)
request.httpBody = jsonData
} catch {
completion(.failure(.encodingError(error)))
return
}
performDataTask(request: request, completion: completion)
}
}
PUT for Resource Creation
extension APIManager {
func createOrUpdateUserSettings(_ settings: UserSettings, userID: String, completion: @escaping (Result<UserSettings, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(userID)/settings") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
// Add conditional headers
if let lastModified = settings.lastModified {
let formatter = DateFormatter()
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
request.setValue(formatter.string(from: lastModified), forHTTPHeaderField: "If-Unmodified-Since")
}
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try encoder.encode(settings)
request.httpBody = jsonData
} catch {
completion(.failure(.encodingError(error)))
return
}
performDataTask(request: request, completion: completion)
}
}
DELETE Requests
DELETE requests remove resources from the server. They should be idempotent - multiple DELETE requests for the same resource should have the same effect.
Basic DELETE Implementation
extension APIManager {
func deleteUser(userID: String, completion: @escaping (Result<Void, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(userID)") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(.networkError(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.invalidResponse))
return
}
switch httpResponse.statusCode {
case 200, 204:
completion(.success(()))
case 404:
completion(.failure(.resourceNotFound))
case 403:
completion(.failure(.forbidden))
default:
completion(.failure(.serverError(httpResponse.statusCode)))
}
}
}
task.resume()
}
}
DELETE with Confirmation
extension APIManager {
func deleteUserAccount(userID: String, password: String, completion: @escaping (Result<DeletionResponse, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(userID)") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
// Add confirmation data
let confirmationData = ["password": password, "confirm_deletion": true] as [String: Any]
do {
let jsonData = try JSONSerialization.data(withJSONObject: confirmationData)
request.httpBody = jsonData
} catch {
completion(.failure(.encodingError(error)))
return
}
performDataTask(request: request, completion: completion)
}
}
Bulk DELETE Operations
extension APIManager {
func deleteMultipleUsers(userIDs: [String], completion: @escaping (Result<BulkDeleteResponse, APIError>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/bulk-delete") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
let bulkDeleteRequest = BulkDeleteRequest(userIDs: userIDs)
do {
let jsonData = try JSONEncoder().encode(bulkDeleteRequest)
request.httpBody = jsonData
} catch {
completion(.failure(.encodingError(error)))
return
}
performDataTask(request: request, completion: completion)
}
}
Advanced Networking
Custom URLSession Configuration
class AdvancedAPIManager {
private let session: URLSession
init() {
let configuration = URLSessionConfiguration.default
// Timeout settings
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
// Cache settings
configuration.requestCachePolicy = .returnCacheDataElseLoad
configuration.urlCache = URLCache(
memoryCapacity: 50 * 1024 * 1024, // 50 MB memory cache
diskCapacity: 100 * 1024 * 1024, // 100 MB disk cache
diskPath: "api_cache"
)
// Connection settings
configuration.allowsCellularAccess = true
configuration.waitsForConnectivity = true
configuration.httpMaximumConnectionsPerHost = 4
// Protocol settings
configuration.protocolClasses = [CustomProtocol.self]
// HTTP settings
configuration.httpCookieAcceptPolicy = .always
configuration.httpShouldSetCookies = true
configuration.httpShouldUsePipelining = true
// Background downloads
configuration.isDiscretionary = false
configuration.shouldUseExtendedBackgroundIdleMode = true
self.session = URLSession(
configuration: configuration,
delegate: self,
delegateQueue: nil
)
}
}
Request Interceptors and Middleware
protocol RequestInterceptor {
func intercept(request: inout URLRequest)
}
class AuthenticationInterceptor: RequestInterceptor {
func intercept(request: inout URLRequest) {
if let token = AuthManager.shared.currentToken {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
}
}
class LoggingInterceptor: RequestInterceptor {
func intercept(request: inout URLRequest) {
print("π \(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")")
}
}
}
class NetworkManager {
private let interceptors: [RequestInterceptor]
init(interceptors: [RequestInterceptor] = []) {
self.interceptors = [
AuthenticationInterceptor(),
LoggingInterceptor()
] + interceptors
}
private func applyInterceptors(to request: inout URLRequest) {
interceptors.forEach { $0.intercept(request: &request) }
}
}
Response Processing Pipeline
protocol ResponseProcessor {
func process<T: Codable>(data: Data, response: URLResponse) throws -> T
}
class DefaultResponseProcessor: ResponseProcessor {
func process<T: Codable>(data: Data, response: URLResponse) throws -> T {
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse
}
// Check content type
if let contentType = httpResponse.allHeaderFields["Content-Type"] as? String {
guard contentType.contains("application/json") else {
throw APIError.invalidContentType
}
}
// Validate status code
guard 200...299 ~= httpResponse.statusCode else {
if let errorData = try? JSONDecoder().decode(APIErrorResponse.self, from: data) {
throw APIError.apiError(errorData)
}
throw APIError.serverError(httpResponse.statusCode)
}
// Decode response
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
return try decoder.decode(T.self, from: data)
} catch {
throw APIError.decodingError(error)
}
}
}
Background Downloads
class BackgroundDownloadManager: NSObject {
static let shared = BackgroundDownloadManager()
private var backgroundSession: URLSession!
private var activeDownloads: [URL: Download] = [:]
override init() {
super.init()
let config = URLSessionConfiguration.background(withIdentifier: "com.app.background-downloads")
config.isDiscretionary = false
config.sessionSendsLaunchEvents = true
backgroundSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
func startDownload(url: URL) {
let download = Download(url: url)
download.task = backgroundSession.downloadTask(with: url)
download.task?.resume()
activeDownloads[url] = download
}
func pauseDownload(url: URL) {
guard let download = activeDownloads[url] else { return }
download.task?.cancel { resumeDataOrNil in
guard let resumeData = resumeDataOrNil else { return }
download.resumeData = resumeData
}
}
func resumeDownload(url: URL) {
guard let download = activeDownloads[url] else { return }
if let resumeData = download.resumeData {
download.task = backgroundSession.downloadTask(withResumeData: resumeData)
} else {
download.task = backgroundSession.downloadTask(with: url)
}
download.task?.resume()
}
}
WebSocket Implementation
@available(iOS 13.0, *)
class WebSocketManager: NSObject {
private var webSocketTask: URLSessionWebSocketTask?
private let session: URLSession
override init() {
self.session = URLSession(configuration: .default)
super.init()
}
func connect(to url: URL) {
webSocketTask = session.webSocketTask(with: url)
webSocketTask?.resume()
receiveMessage()
}
func disconnect() {
webSocketTask?.cancel(with: .goingAway, reason: "User initiated disconnect".data(using: .utf8))
}
func sendMessage(_ message: String) {
let message = URLSessionWebSocketTask.Message.string(message)
webSocketTask?.send(message) { error in
if let error = error {
print("WebSocket send error: \(error)")
}
}
}
func sendData(_ data: Data) {
let message = URLSessionWebSocketTask.Message.data(data)
webSocketTask?.send(message) { error in
if let error = error {
print("WebSocket send error: \(error)")
}
}
}
private func receiveMessage() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
self?.handleMessage(message)
self?.receiveMessage() // Continue receiving
case .failure(let error):
print("WebSocket receive error: \(error)")
}
}
}
private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
switch message {
case .string(let text):
print("Received text: \(text)")
case .data(let data):
print("Received data: \(data.count) bytes")
@unknown default:
break
}
}
}
Error Handling
Robust error handling is crucial for a good user experience and debugging. Here's a comprehensive error handling system:
Custom Error Types
enum APIError: Error, LocalizedError {
case invalidURL
case noData
case invalidResponse
case networkError(Error)
case serverError(Int)
case decodingError(Error)
case encodingError(Error)
case authenticationRequired
case forbidden
case resourceNotFound
case rateLimitExceeded
case invalidContentType
case apiError(APIErrorResponse)
// MARK: - LocalizedError
var errorDescription: String? {
switch self {
case .invalidURL:
return "Invalid URL"
case .noData:
return "No data received"
case .invalidResponse:
return "Invalid response"
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .serverError(let code):
return "Server error with status code: \(code)"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
case .encodingError(let error):
return "Encoding error: \(error.localizedDescription)"
case .authenticationRequired:
return "Authentication required"
case .forbidden:
return "Access forbidden"
case .resourceNotFound:
return "Resource not found"
case .rateLimitExceeded:
return "Rate limit exceeded"
case .invalidContentType:
return "Invalid content type"
case .apiError(let apiError):
return apiError.message
}
}
// MARK: - Retry logic
var isRetryable: Bool {
switch self {
case .networkError:
return true // e.g. connectivity issues
case .serverError(let code):
// Retry for 5xx errors
return (500...599).contains(code)
case .rateLimitExceeded:
return true // can retry after backoff
default:
return false
}
}
}
Comments
Post a Comment