Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Releases: Aldo10012/EZNetworking

5.4.0

17 Feb 00:44
4b013b4

Choose a tag to compare

What's new?

Both of my WebSocketClient and ServerSentEventClient are entirely Swift Concurrency-based, with no support for callbacks or publishers. In order to support callbacks and publishers for any user who is still relying on the old paradigms, I created adaptors.

Websocket adaptors

Callback adaptor

/// init
let ws = WebSocketCallbackAdapter(url: "some_websocket_url")

/// connect
ws.connect { result in
    // handle result
}

/// disconnect
ws.disconnect { result in
    // handle result
}

/// terminate
ws.terminate()

/// send message
ws.send(.string("Hello")) { result in
    // handle result
}

/// receive messages
ws.onMessage { result in
    // handle result
}

/// state change
ws.onStateChange { result in
    // handle result
}

Publisher adaptor

let ws = WebSocketPublisherAdapter(url: "some_websocket_url")

/// connect
ws.connect()
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// disconnect
ws.disconnect()
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// terminate
ws.terminate()

/// send message
ws.send(.string("Hello"))
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// receive messages
ws.messages
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// state change
ws.stateEvents
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

ServerSentEvent adaptors

Callback adaptor

/// init
let sse = ServerSentEventCallbackAdapter(url: "some_websocket_url")

/// connect
sse.connect { result in
    // handle result
}

/// disconnect
sse.disconnect { result in
    // handle result
}

/// terminate
sse.terminate()

/// receive event
ws.onEvent { result in
    // handle result
}

/// state change
ws.onStateChange { result in
    // handle result
}

Publisher adaptor

let sse = ServerSentEventPublisherAdapter(url: "some_websocket_url")

/// connect
sse.connect()
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// disconnect
sse.disconnect()
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// terminate
sse.terminate()

/// receive events
sse.onEvent
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

/// state change
ws.stateEvents
    .sink(
        // handle sink
    )
    .store(in: &cancellables)

5.3.3

16 Feb 21:06
56cf45a

Choose a tag to compare

What's new?

  • Update Error Handling
  • Update Response Validation

Updated Error Handling

Replace WebSocketError with NetworkingError.webSocketFailure(reason: _)
Replace SSEError with NetworkingError.serverSentEventFailure(reason: _)

Updated Response Validation

Rename ResponseValidatorImpl to DefaultResponseValidator
Also added an ooptional init argument for expected Response Headers

Example: DefaultResponseValidator(expectedHttpHeaders: [.contentType(.eventStream)]

5.3.2

16 Feb 07:38
9ea89ff

Choose a tag to compare

What's new?

Renamed SSEConnectionConfig to RetryPolicy

5.3.1

16 Feb 07:36

Choose a tag to compare

What's new?

Added a convenience method to allow for initialization of ServerSentEventManager using a simple url string

let manager = ServerSentEventManager(url: "https://api.example.com/events")

5.3.0

14 Feb 09:13
375956b

Choose a tag to compare

What's new?

Adding support for Server-Sent Events (SSE)

Server-Sent Events enable real-time, unidirectional streaming from server to client over standard HTTP—perfect for live feeds, dashboards, notifications, and any scenario where only the server needs to push updates.

What are Server-Sent Events?

Server-Sent Events (SSE) is a standard protocol that allows servers to push real-time updates to clients over a persistent HTTP connection. Unlike WebSockets, which provide bidirectional communication, SSE is optimized for one-way data flow from server to client.

How it works:

  • Client establishes a long-lived HTTP connection with Content-Type: text/event-stream
  • Server keeps the connection open and pushes events whenever new data is available
  • Client receives events as a continuous stream without making repeated requests

Key characteristics:

  • Unidirectional: Server pushes to client (client doesn't send messages back)
  • Built on HTTP: No protocol upgrade—works over standard HTTP/HTTPS
  • Automatic reconnection: Built-in reconnection with exponential backoff
  • Event IDs: Resume streams from where you left off (no duplicates or gaps)
  • Lightweight: Simple text-based protocol, lower overhead than WebSockets

Common Use Cases

Live dashboards — Real-time metrics and monitoring
Stock/crypto tickers — Continuous price updates
News feeds — Push notifications for breaking news
Social media updates — New posts, likes, comments
Sports scores — Live game updates
Server monitoring — Log streaming and status updates

Read more about SSE: MDN Web Docs - Server-sent events

How to use?

How to initialize

let request = SSERequest(
    url: "https://api.example.com/events",
    additionalheaders: [
        .authorization(.bearer("your-token")),
        .customHeader(name: "X-Client-ID", value: "ios-app")
    ]
)
let manager = ServerSentEventManager(request: request)

// Optional: Configure automatic reconnection
let reconnectionConfig = SSEReconnectionConfig(
    enabled: true,
    maxAttempts: 5,          // nil for unlimited
    initialDelay: 1.0,       // seconds
    maxDelay: 60.0,          // seconds
    backoffMultiplier: 2.0   // exponential backoff
)
let manager = ServerSentEventManager(
    url: "https://api.example.com/events",
    reconnectionConfig: reconnectionConfig
)

How to connect

try await manager.connect()

How to disconnect

try await manager.disconnect()

How to terminate

try await manager.terminate()

How to Receive Events

for await event in manager.events {
    print("Received:", event.data)
    
    // Access event fields
    if let id = event.id {
        print("Event ID:", id)
    }
    
    if let type = event.event {
        print("Event type:", type)
    }
    
    if let retry = event.retry {
        print("Server retry interval:", retry, "ms")
    }
}

How to handle different event types

for await event in manager.events {
    guard let data = event.data.data(using: .utf8) else { return }
    
    switch event.event {
    case "price_update":
        let price = try? JSONDecoder().decode(StockPrice.self, from: data)
        updateUI(with: price)
        
    case "notification":
        let notification = try? JSONDecoder().decode(Notification.self, from: data)
        showAlert(notification)
        
    default:
        // Handle default "message" type
        print("Message:", event.data)
    }
}

How to Observe Connection State

for await state in manager.stateEvents {
    switch state {
    case .notConnected:
        // Initial state before connecting
        statusLabel.text = "Offline"
        
    case .connecting:
        // Connection attempt in progress
        statusLabel.text = "Connecting..."
        
    case .connected:
        // Successfully connected and streaming
        statusLabel.text = "Live"
        
    case .disconnected(let reason):
        // Connection lost
        switch reason {
        case .streamEnded, .streamError:
            // Automatic reconnection will occur if configured
            statusLabel.text = "Reconnecting..."
        case .manuallyDisconnected:
            statusLabel.text = "Offline"
        case .terminated:
            statusLabel.text = "Disconnected"
        }
    }
}

5.2.0

07 Feb 03:09

Choose a tag to compare

What's new?

Updated NetworkingError enum.

public enum NetworkingError: Error {
    case couldNotBuildURLRequest(reason: URLBuildFailureReason)
    case decodingFailed(reason: DecodingFailureReason)
    case responseValidationFailed(reason: ResponseValidationFailureReason)
    case requestFailed(reason: RequestFailureReason)
}

public enum DecodingFailureReason: Equatable, Sendable {
    case decodingError(underlying: DecodingError)
    case other(underlying: Error)
}

public enum RequestFailureReason: Equatable, Sendable {
    case urlError(underlying: URLError)
    case unknownError(underlying: Error)
}

public enum ResponseValidationFailureReason: Equatable, Sendable {
    case noHTTPURLResponse
    case badHTTPResponse(underlying: HTTPResponse)
}

public enum URLBuildFailureReason: Equatable, Sendable {
    case noURL
    case invalidURL
    case invalidScheme(String?)
    case missingHost
}

Error handling example

do {
    let response = try await AsyncRequestPerformer().perform(request: request, decodeTo: UserData.self)
    // do something with the response
} catch let error as NetworkingError {
    switch error {
    case .couldNotBuildURLRequest(reason: let reason):
        // url inside of your Request is invalid
    case .decodingFailed(reason: let reason):
        // could not decode data into your Decodable type
    case .responseValidationFailed(reason: let reason):
        // bad URLResponse
    case .requestFailed(reason: let reason):
        // api request failed
    }
}

5.1.0

06 Feb 08:03

Choose a tag to compare

What's new?

This patch is mainly an internal refactoring for Swift concurrency.

The biggest difference impacting clients is the return type of the following methods:

  • RequestPerformable.performTask(...) -> URLSessionDataTask
  • FileDownloadable.downloadFileTask(...) -> URLSessionDownloadTask
  • DataUploadable.uploadDataTask(...) -> URLSessionUploadTask
  • FileUploadable.uploadFileTask(...) -> URLSessionUploadTask

Each of these methods no longer returns subclasses of URLSessionTask. Instead, they now return a new abstract CancellableRequest.

Why was this change made?

Each of the protocols listed above has variants of the same method to support multiple programming paradigms (Swift Concurrency, completion handlers, Combine). To prevent having repeated internal logic, one method was chosen as the "core" implementation, while the rest are adapter methods.

Previously, I was using the completion handler-based method (using URLSession.dataTask() and similar methods) as the root implementation, while all other methods were adapters. I also returned the URLSessionTask in order to allow clients the ability to call methods such as .suspend() or .cancel().

However, Apple has recommended that all new networking code moving forward be written in Swift Concurrency, and to adapt to completion handlers or Publishers for legacy support. Following that recommendation, I refactored every networking class to use async/await as the core implementation and have the other methods be adapters.

Before

RequestPerformer.perform() - adapter
RequestPerformer.performTask() - CORE
RequestPerformer.performPublisher() - adapter

After

RequestPerformer.perform() - CORE
RequestPerformer.performTask() - adapter
RequestPerformer.performPublisher() - adapter

This means that I am no longer internally using URLSession.dataTask() (and similar methods) but rather URLSession.data() (and similar methods), meaning I no longer have access to the URLSessionTask objects.

In order to keep consistent behavior, I created a new CancellableRequest class which mimics URLSessionTask as closely as possible, with .resume() and .cancel() methods. Calling .cancel() is also able to also cancel the underlying Task used, thus also being able to stop the internal Swift Concurrency task.

5.0.4

24 Jan 06:17
636cd71

Choose a tag to compare

What's new?

Updated how session management is done

Now instead of injecting a URLSession and a SessionDelegate into different request performing classes, clients now inject Session which internally holds those values.

Before

let urlSession = URLSession(...)
let delegate: SessionDelegate()
RequestPerformer(urlSession: urlSession, delegate: delegate)

After

let delegate: SessionDelegate()
let session = Session(delegate: delegate)
RequestPerformer(session: session)

Why did I make these changes?

A) easier to manage
B) reduce large init methods since I enforce URLSession.delegate to be of type SessionDelegate in order for different interceptors to work properly.

5.0.3

15 Jan 08:30
86eb317

Choose a tag to compare

What's new?

Update package to support the following platforms:

  • iOS (already supported)
  • macOS
  • watchOS
  • tvOS
  • visionOS

Support the following Swift versions:

  • 5.10
  • 6.0
  • 6.1
  • 6.2

5.0.2

12 Jan 02:35
07d0c75

Choose a tag to compare

What's new?

Improved URL validation