A basic networking layer using combine framework
- Authors
- Shubham Kumar
Introduction
Apple’s Combine framework is a declarative Swift API introduced in iOS 13 that allows you to process values over time. It provides a powerful way to work with asynchronous data streams using publishers, subscribers, and operators — similar in concept to Reactive frameworks like RxSwift.
Combine is a reactive programming framework. It unifies handling asynchronous events using a stream-based model. It promotes declarative coding — describing what should happen, not how.
Core Concepts of Combine
- Publisher: Emits values over time (like URLSession data task, NotificationCenter, etc.).
- Subscriber: Receives values and completion from a publisher.
- Operator: Transforms, filters, combines, and manipulates values emitted by publishers.
- Subject: A publisher you can manually control — useful for bridging non-Combine code.
Benefits of Combine for Networking
- Clear handling of success/failure.
- Composable transformations and chaining.
- Declarative and concise.
- Easy integration with SwiftUI via Published, ObservableObject.
How Does Combine Work?
- Something happens (like pressing a button, or data coming from the internet).
- That thing becomes a Publisher.
- You connect it to a Subscriber.
- In between, you can use Operators to filter or modify the data.
- The Subscriber gets the final result.
Creating a Network Layer using Combine
Network dependency container
The Network Dependency Container serves as a centralized class for managing all network-related dependencies, such as API clients, request builders, and configuration settings. It acts as a shared access point that ensures consistent and controlled usage of networking services across all modules or frameworks within the application architecture.
A shared instance: The NetworkDependencyContainer is designed as a shared instance, allowing all modules across the application to access a consistent, pre-configured set of networking services. This promotes uniform behavior and eliminates the need for redundant configuration.- In most scenarios, there is no need to create multiple instances of the container, as a single configuration is sufficient for the entire application lifecycle. However, change is needed to support multiple instances when needed — such as in iPadOS or macOS environments, where multi-window scenes may require separate networking contexts.
networkTransferService: The container provides access to a centralized networkTransferService, which serves as the primary interface for performing all network interactions. This ensures a consistent and testable approach to API across all modules.- The container can be extended to include various network-related configurations, such as timeout settings, logging, headers, and authentication strategies.
DataSwitch: The DataSwitch component within the container enables toggling between real network calls and mock data. Each module can check this flag and behave accordingly, making it easier to test and develop features.
DataSwitch
NetworkRequestRouter
In a modular architecture, each framework may define its own network configuration. While a global configuration is available and recommended for consistency, individual frameworks are allowed the flexibility to override defaults with custom settings when necessary.
url- mandatorymethodType- mandatoryheaders- mandatorybody- optionalquery items- optional
A protocol must be created, so other modules can confirm and create their own configs.
Helper Types and Enums for Network Request Router
The NetworkRequestRouter is designed to standardize how network requests are constructed across the application. It encapsulates all essential request components while leveraging default configuration from the shared NetworkDependencyContainer. For router following details will be computed by default unless overriden:
fullUrl- computed internally by combining the baseUrl and path. Query parameters are appended automatically via URLComponents - can be overridenbaseUrl- The router utilizes default baseUrl from the shared NetworkDependencyContainer - can be overriddenheaders- The router utilizes default headers from the shared NetworkDependencyContainer - can be overridden
Network Service
The NetworkService is a utility that acts as the bridge between a request router (NetworkRequestRouter) and the actual network layer powered by URLSession.
Network Service is specific to this module only, and will not be exposed to other frameworks. This will be used by NetworkTransferService to implements its method.
- NetworkService will use
dataTaskPublisherto make the network call - NetworkService will also handle the errors related to network calls and dataTaskPublisher.
- NetworkService will have a NetworkSession, which will be added as dependencies which mainly contains implementation of network calls.
- NetworkService will have a request method, which converts the data provided by NetworkSession.
DefaultNetworkServiceis the default NetworkService implementation.
Helper Types and Enums for NetworkService
Using helpers to create request using network session.
NetworkTransferService
NetworkTransferService acts as the networking service for other frameworks or modules. It abstracts the lower-level functionalities NetworkService.
Protocol for the NetworkTransferService should contain methods which decodes the data and errors provided by the Network Service as per the requirement.
Helper functions for Network Transfer Service
Main protocols:
-
fetchCodable: Fetches data from the network, attempts to decode it into a generic Decodable model (C), and returns the decoded model wrapped in a Combine publisher.
- Calls the underlying networkService.request with the provided router (request details).
- Converts any low-level network errors into NetworkTransferError using resolve(networkError:).
- Ensures the response data is non-nil; throws noResponse error if missing.
- Attempts to decode the data into the specified Decodable type using JSONDecoder.
- Any decoding errors are converted into NetworkTransferError.parsing.
- Catches errors explicitly and returns a failing publisher (mostly for clarity; Combine handles failures by default).
- Erases the publisher to a generic AnyPublisher type for abstraction.
-
fetchResponse: Performs a network request and processes the raw data response into a standardized SuccessResponse model (likely a generic wrapper indicating success or status).
- Uses networkService.request to perform the request.
- Converts network errors to NetworkTransferError with resolve(networkError:).
- Validates that response data is present.
- Attempts to parse raw data into a JSON object or a structured SuccessResponse using parseDataToJSON(data:).
- Throws noResponse error if data is missing or parsing fails.
- Catches and returns errors as a failed Combine publisher.
- Erases to AnyPublisher for use by consumers.
-
fetchLocalData: Loads JSON data from a local file bundled within the app if mock is enabled, parses it into a Decodable model, and returns it as a Combine publisher.
- Uses a Future publisher to perform synchronous file read and decoding.
- Attempts to find the file in the main bundle and read its contents.
- If file is missing or reading fails, returns noResponse error.
- Parses the data into the specified model type using parseDataToModel(data:decodingType:).
- Returns success with the parsed model or failure if parsing fails.
- Erases to AnyPublisher.
-
get: - A convenience wrapper over fetchCodable for HTTP GET requests.
-
post, put, delete: - Convenience wrappers for HTTP POST, PUT, and DELETE requests respectively, returning a SuccessResponse.
Related Posts
View all- A lorem ipsum fixture that exercises headings, links, lists, tables, code, math, images, callouts, and other MDX presentation features.
- A short collaborative lorem ipsum post used to verify multiple bylines, individual author profiles, tag pages, and related-post recommendations.
- A nested-slug lorem ipsum fixture for validating catch-all blog routes, canonical post data, author pages, images, and generated metadata.

