A modern, clean, and testable networking layer for iOS and macOS, built with Swift's async/await.
YFGNetwork is a powerful yet lightweight networking framework designed to abstract away the complexities of URLSession. It provides a type-safe way to define API endpoints and handles common networking tasks like request interception, response validation, and automatic retries. Its protocol-oriented design makes it incredibly easy to test and extend.
-
✅ Modern Concurrency: Built entirely on
async/awaitfor clean, readable asynchronous code. -
✅ Type-Safe Endpoints: Define your API endpoints safely using enums.
-
✅ SOLID Principles: Designed for testability and scalability from the ground up.
-
✅ Request Interception: Easily add authentication tokens or common headers to requests.
-
✅ Response Validation: Automatic validation of status codes and response data.
-
✅ Automatic Retries: Configurable retry policies for handling transient network failures.
-
✅ Detailed Logging: A built-in logger for easy debugging of network traffic.
-
✅ Multipart & Data Uploads: Full support for complex uploads.
-
✅ File Downloads: Simple API for downloading files directly to a destination.
-
iOS 13.0+
-
macOS 10.15+
-
Swift 5.5+
You can add YFGNetwork to your project by adding it as a package dependency in Xcode or your Package.swift file.
-
In Xcode, go to File > Add Packages...
-
Enter the repository URL:
https://github.com/Yefga/YFGNetwork.git
Or, add it to your Package.swift:
dependencies: [
.package(url: "[https://github.com/Yefga/YFGNetwork.git](https://github.com/Yefga/YFGNetwork.git)", from: "1.0.0")
]
To install with CocoaPods, add the following line to your Podfile:
pod 'YFGNetwork'
Then, run pod install.
Using YFGNetwork involves four main steps: defining your environment, defining your endpoints, creating the service, and making requests.
Create a struct that conforms to YFGEnvironment to specify the base URL for your API.
import YFGNetwork
struct APIEnvironment: YFGEnvironment {
var baseURL: URL {
return URL(string: "[https://api.example.com/v1](https://api.example.com/v1)")!
}
}
Create an enum that conforms to YFGEndpoint. This is where you define all the specifics for each API call.
import YFGNetwork
// First, define the data models you expect from the API.
struct UserProfile: Decodable {
let id: String
let name: String
let email: String
}
struct PublicPost: Decodable {
let id: Int
let title: String
let body: String
}
// Next, define the endpoints.
enum MyAPI {
case getPublicPosts
case getUserProfile(userId: String)
case updateUser(userId: String, name: String)
}
extension MyAPI: YFGEndpoint {
var path: String {
switch self {
case .getPublicPosts:
return "/posts"
case .getUserProfile(let userId):
return "/users/\(userId)"
case .updateUser(let userId, _):
return "/users/\(userId)"
}
}
var method: YFGHTTPMethod {
switch self {
case .getPublicPosts, .getUserProfile:
return .get
case .updateUser:
return .patch
}
}
var task: YFGTask {
switch self {
case .getPublicPosts, .getUserProfile:
// No parameters or body needed for these GET requests.
return .requestPlain
case .updateUser(_, let name):
// Send an encodable object as the request body.
let params = ["name": name]
return .requestJSONEncodable(params)
}
}
var headers: [String: String]? {
// You can provide default headers for all endpoints.
return ["Content-Type": "application/json"]
}
var needsInterceptor: Bool {
switch self {
case .getPublicPosts:
// This is a public endpoint and does not need an auth token.
return false
case .getUserProfile, .updateUser:
// These endpoints require authentication.
return true
}
}
// You can also override retryPolicy, cachePolicy, etc. here if needed.
}
Instantiate YFGNetworkService with your environment. You can also inject custom interceptors, loggers, or validators.
import YFGNetwork
let networkService = YFGNetworkService(environment: APIEnvironment())
Use an async function to call the service and handle the response.
func fetchUserProfile() async {
do {
let userProfile: UserProfile = try await networkService.request(.getUserProfile(userId: "123"))
print("Successfully fetched user: \(userProfile.name)")
} catch {
// The error will be a YFGNetworkError, so you can handle it specifically.
print("Failed to fetch user profile: \(error.localizedDescription)")
}
}
// Call the async function
Task {
await fetchUserProfile()
}
For tasks like refreshing authentication tokens, you can provide a custom interceptor.
class TokenInterceptor: YFGRequestInterceptor {
private let authManager: AuthManager // Your class that handles token storage
init(authManager: AuthManager) {
self.authManager = authManager
}
func adapt(_ request: URLRequest) async throws -> URLRequest {
var mutableRequest = request
// Get a valid token, potentially refreshing it if it's expired.
let token = await authManager.getValidToken()
mutableRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
return mutableRequest
}
}
YFGNetwork is released under the MIT license. See LICENSE for details.