CocoaPods trunk is moving to be read-only. Read more on the blog, there are 9 months to go.

YFGNetwork 0.1.0

YFGNetwork 0.1.0

Maintained by [email protected].



  • By
  • Yefga Torra

A modern, clean, and testable networking layer for iOS and macOS, built with Swift's async/await.

Overview

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.

Features

  • Modern Concurrency: Built entirely on async/await for 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.

Requirements

  • iOS 13.0+

  • macOS 10.15+

  • Swift 5.5+

Installation

Swift Package Manager

You can add YFGNetwork to your project by adding it as a package dependency in Xcode or your Package.swift file.

  1. In Xcode, go to File > Add Packages...

  2. 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")
]

CocoaPods

To install with CocoaPods, add the following line to your Podfile:

pod 'YFGNetwork'

Then, run pod install.

How to Use

Using YFGNetwork involves four main steps: defining your environment, defining your endpoints, creating the service, and making requests.

1. Define Your API Environment

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)")!
    }
}

2. Define Your Endpoints

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.
}

3. Create the Network Service

Instantiate YFGNetworkService with your environment. You can also inject custom interceptors, loggers, or validators.

import YFGNetwork

let networkService = YFGNetworkService(environment: APIEnvironment())

4. Make a Request

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()
}

Advanced: Custom Request Interceptor

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
    }
}

License

YFGNetwork is released under the MIT license. See LICENSE for details.