SRNetworkManager 🚀

SRNetworkManager 是一个为 Swift 应用设计的强大灵活的网络层。它为处理 API 请求提供了一种通用、面向协议的方法,同时支持 Combineasync/await 范式。此软件包旨在 易于使用高度可定制,并与 Swift 6Sendable 协议 完全兼容


Platform Swift License Version

🎯 功能特性


📋 系统要求


📦 安装

Swift Package Manager (SPM)

将以下内容添加到您的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/siamakrostami/SRNetworkManager.git", from: "1.0.0")
]

或使用 Xcode

  1. 前往 File > Add Packages...
  2. 搜索
    https://github.com/siamakrostami/SRNetworkManager.git
    
  3. 选择最新版本并将其添加到您的项目中。

📚 用法

以下是如何使用 SRNetworkManager 进行 网络请求 以及 网络监控VPN 检测 的示例。

1. 网络请求

初始化 APIClient

let client = APIClient() // Basic initialization with default settings

let client = APIClient(qos: .background) // Initialization with custom QoS (Quality of Service)

let client = APIClient(logLevel: .verbose) // Initialization with custom log level

let client = APIClient(qos: .userInitiated, logLevel: .standard) // With QoS + log level

let client = APIClient(retryHandler: MyCustomRetryHandler()) // With a custom retry handler

let client = APIClient(decoder: MyCustomDecoder()) // With a custom decoder

定义 API 端点

struct UserAPI: NetworkRouter {
    typealias Parameters = UserParameters
    typealias QueryParameters = UserQueryParameters

    var baseURLString: String { "https://api.example.com" }
    var method: RequestMethod? { .get }
    var path: String { "/users" }
    var headers: [String: String]? { 
        HeaderHandler.shared
            .addAcceptHeaders(type: .applicationJson)
            .addContentTypeHeader(type: .applicationJson)
            .build() 
    }
    var params: Parameters? { UserParameters(id: 123) }
    var queryParams: QueryParameters? { UserQueryParameters(includeDetails: true) }
}

或使用仓库风格的方法

public protocol SampleRepositoryProtocols: Sendable {
    func getInvoice(documentID: String) -> AnyPublisher<SomeModel, NetworkError>
    func getInvoice(documentID: String) async throws -> SomeModel
    
    func getReceipt(transactionId: String) -> AnyPublisher<SomeModel, NetworkError>
    func getReceipt(transactionId: String) async throws -> SomeModel
}

public final class SampleRepository: Sendable {
    // MARK: Lifecycle

    public init(client: APIClient) {
        self.client = client
    }

    // MARK: Private

    private let client: APIClient
}

extension SampleRepository {
    enum Router: NetworkRouter {
        case getInvoice(documentID: String)
        case getReceipt(transactionId: String)

        var path: String {
            switch self {
            case .getInvoice(let documentID):
                return "your/path/\(documentID)"
            case .getReceipt(let transactionId):
                return "your/path/\(transactionId)"
            }
        }

        var method: RequestMethod? {
            switch self {
            case .getInvoice:
                return .get
            case .getReceipt:
                return .post
            }
        }

        var headers: [String: String]? {
            var handler = HeaderHandler.shared
                .addAuthorizationHeader()
                .addAcceptHeaders(type: .applicationJson)
                .addDeviceId()
            
            switch self {
            case .getInvoice:
                break
            case .getReceipt:
                handler = handler.addContentTypeHeader(type: .applicationJson)
            }
            
            return handler.build()
        }
        
        var queryParams: SampleRepositoryQueryParamModel? {
            switch self {
            case .getInvoice(let trxId):
                return SampleRepositoryQueryParamModel(trxId: trxId)
            case .getReceipt(let transactionId):
                return SampleRepositoryQueryParamModel(trxId: transactionId)
            }
        }
        
        var params: SampleRepositoryQueryParamModel? {
            switch self {
            case .getInvoice(let documentID):
                return SampleRepositoryQueryParamModel(
                    documentId: documentID,
                    stepId: "Some Id",
                    subStepId: "Some Id"
                )
            case .getReceipt:
                return nil
            }
        }
    }
}

extension SampleRepository: SampleRepositoryProtocols {
    public func getInvoice(documentID: String) -> AnyPublisher<SomeModel, NetworkError> {
        client.request(Router.getInvoice(documentID: documentID))
    }
    
    public func getInvoice(documentID: String) async throws -> SomeModel {
        try await client.asyncRequest(Router.getInvoice(documentID: documentID))
    }
    
    public func getReceipt(transactionId: String) -> AnyPublisher<SomeModel, NetworkError> {
        client.request(Router.getReceipt(transactionId: transactionId))
    }
    
    public func getReceipt(transactionId: String) async throws -> SomeModel {
        try await client.asyncRequest(Router.getReceipt(transactionId: transactionId))
    }
}

public struct SampleRepositoryQueryParamModel: Codable, Sendable {
    public init(documentId: String? = nil,
                stepId: String? = nil,
                subStepId: String? = nil,
                trxId: String? = nil) {
        self.documentId = documentId
        self.stepId = stepId
        self.subStepId = subStepId
        self.trxId = trxId
    }
    
    public let documentId: String?
    public let stepId: String?
    public let subStepId: String?
    public let trxId: String?
}

发起请求 (async/await) ⚡

Task {
    do {
        let userResponse: UserResponse = try await client.asyncRequest(UserAPI())
        print("Received user: \(userResponse)")
    } catch {
        print("Request failed: \(error)")
    }
}

发起请求 (Combine) 🔗

let apiClient = APIClient()

apiClient.request(UserAPI())
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Request completed successfully")
        case .failure(let error):
            print("Request failed with error: \(error)")
        }
    }, receiveValue: { (response: UserResponse) in
        print("Received user: \(response)")
    })
    .store(in: &cancellables)

文件上传 📤

let apiClient = APIClient()

let fileData = // ... your file data ...
let endpoint = UploadAPI()

apiClient.uploadRequest(endpoint, withName: "file", data: fileData) { progress in
    print("Upload progress: \(progress)")
}
.sink(receiveCompletion: { completion in
    // Handle completion
}, receiveValue: { (response: UploadResponse) in
    print("Upload completed: \(response)")
})
.store(in: &cancellables)

2. 网络监控

SRNetworkManager 提供了一个简单的实用工具,通过 NWPathMonitor 监控网络状态,公开了

这是一个使用示例

import Combine

var cancellables = Set<AnyCancellable>()

// Instantiate the network monitor (optionally disabling VPN detection)
let network = NetworkMonitor(shouldDetectVpnAutomatically: true)

// Start monitoring
network.startMonitoring()

// 1) Combine subscription
network.status
    .sink { status in
        switch status {
        case .disconnected:
            debugPrint("disconnected")
        case .connected(let networkType):
            switch networkType {
            case .wifi:
                debugPrint("wifi")
            case .cellular:
                debugPrint("cellular")
            case .ethernet:
                debugPrint("ethernet")
            case .other:
                debugPrint("other")
            case .vpn:
                debugPrint("vpn")
            }
        }
    }
    .store(in: &cancellables)

// 2) Async Stream
Task {
    let statusStream = network.statusStream()
    for await status in statusStream {
        switch status {
        case .disconnected:
            debugPrint("Async disconnected")
        case .connected(let type):
            debugPrint("Async connected: \(type)")
        }
    }
}

3. VPN 检查

SRNetworkManager 包含一个独立的 VPNChecker 类,用于检查 VPN 是否处于活动状态。它检查系统的代理设置以查找已知的 VPN 接口。如果需要,您可以独立使用它

let checker = VPNChecker() // Normal usage
let isVPNActive = checker.isVPNActive()
print("VPN Active? \(isVPNActive)")

如果您想绕过 VPN 检查(例如,在调试模式下),您可以使用以下方式初始化

let checker = VPNChecker(shouldBypassVpnCheck: true)

这将始终为 isVPNActive() 返回 false


🔧 自定义

重试处理 🔄

struct CustomRetryHandler: RetryHandler {
    // MARK: Lifecycle

    init(numberOfRetries: Int) {
        self.numberOfRetries = numberOfRetries
    }

    // MARK: Public

    let numberOfRetries: Int

    func shouldRetry(request: URLRequest, error: NetworkError) -> Bool {
        // Implement your logic here
    }

    func modifyRequestForRetry(client: APIClient, request: URLRequest, error: NetworkError) -> (URLRequest, NetworkError?) {
        // Implement your logic here
    }
}

📱 示例 SwiftUI 应用

提供了一个示例 SwiftUI 应用,以帮助您在真实场景中开始使用 SRNetworkManager

示例应用的功能:

获取示例应用:

  1. 克隆此仓库。
  2. 导航到 Example/SRNetworkManagerExampleApp
  3. 在 Xcode 中打开 SRNetworkManagerExampleApp.xcodeproj
  4. 运行项目。

🤝 贡献

欢迎贡献!请随时在 GitHub 上开启 issue 和提交 pull request。


📄 许可证

SRNetworkManagerMIT 许可证 下可用。有关更多详细信息,请参阅 LICENSE 文件。