A modern, thread-safe Swift networking library for iOS, macOS, and other Apple platforms, designed with Swift concurrency in mind.
- β Swift Concurrency built-in with async/await support
- β Strongly typed requests and responses
- β Configurable request building and session management
- β Comprehensive error handling
- β Support for multipart form uploads and raw file uploads
- β Thread-safe token management for authentication
- β Detailed debug logging
- β Easy mocking for unit tests
- β Protocol-oriented design for flexibility and testability
Add NetSwift to your project by adding it as a dependency in your Package.swift file:
dependencies: [
.package(url: "https://github.com/bibintomj/NetSwift.git", from: "1.0.0")
]import NetSwift
// 1. Define your API endpoints
enum UserEndpoint: NetworkRequest {
case getUser(id: Int)
case updateProfile(User)
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String {
switch self {
case .getUser(let id):
return "/users/\(id)"
case .updateProfile:
return "/users/profile"
}
}
var method: HTTPMethod {
switch self {
case .getUser: return .get
case .updateProfile: return .put
}
}
var headers: [String: String]? {
return ["Content-Type": "application/json"]
}
var queryParameters: [String: String]? {
return nil
}
var body: Encodable? {
switch self {
case .getUser: return nil
case .updateProfile(let user): return user
}
}
}
// 2. Create a client
let networkClient = NetworkClient()
// 3. Make requests
struct User: Codable {
var id: Int
var name: String
var email: String
}
// GET request
func fetchUser(id: Int) async throws -> User {
let request = UserEndpoint.getUser(id: id)
return try await networkClient.request(request)
}
// PUT request
func updateUser(_ user: User) async throws -> User {
let request = UserEndpoint.updateProfile(user)
return try await networkClient.request(request)
}NetSwift provides built-in token management with SessionManager:
// After successful login, store tokens
await SessionManager.shared.setTokens(
accessToken: "your-access-token",
refreshToken: "your-refresh-token"
)
// Use ProtectedRequestBuilder for authenticated requests
let protectedClient = NetworkClient(
requestBuilder: ProtectedRequestBuilder()
)
// Tokens will automatically be added to requests
let userProfile: UserProfile = try await protectedClient.request(UserEndpoint.getProfile)
// Clear tokens on logout
await SessionManager.shared.clearTokens()struct ImageUploadRequest: RawFileUploadRequest {
let fileData: Data
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String { "/uploads/images" }
var method: HTTPMethod { .post }
var headers: [String: String]? { ["Content-Type": "image/jpeg"] }
var queryParameters: [String: String]? { nil }
var body: Encodable? { nil }
}
// Upload an image file
let imageData = /* your image data */
let request = ImageUploadRequest(fileData: imageData)
let response: UploadResponse = try await networkClient.uploadBinary(request)struct ProfileImageUploadRequest: MultipartUploadRequest {
let fileData: Data
let fileName: String
let mimeType: String
let formFields: [String: String]
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String { "/users/profile-image" }
var method: HTTPMethod { .post }
var headers: [String: String]? { nil } // Content-Type will be set automatically
var queryParameters: [String: String]? { nil }
var body: Encodable? { nil }
}
// Upload a profile image with additional form fields
let imageData = /* your image data */
let request = ProfileImageUploadRequest(
fileData: imageData,
fileName: "profile.jpg",
mimeType: "image/jpeg",
formFields: ["userId": "123", "description": "Profile photo"]
)
let response: UploadResponse = try await networkClient.uploadMultipart(request)let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
configuration.httpCookieStorage = nil
let session = URLSession(configuration: configuration)
let networkClient = NetworkClient(session: session)let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
let networkClient = NetworkClient(decoder: decoder, encoder: encoder)// Enable detailed network logs in debug builds
let networkClient = NetworkClient(debugLoggingEnabled: true)Sample output:
πβ¬οΈ [NETWORK REQUEST]
π GET https://api.example.com/users/123
π HEADERS:
{
"Content-Type" : "application/json",
"Authorization" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
πβ¬οΈ [NETWORK RESPONSE]
π’ STATUS: 200
π URL: https://api.example.com/users/123
π¦ RESPONSE DATA:
{
"id" : 123,
"name" : "John Doe",
"email" : "[email protected]"
}
πβ
[DECODED RESPONSE]
User(id: 123, name: "John Doe", email: "[email protected]")
NetSwift provides comprehensive error types for different failure scenarios:
do {
let user: User = try await networkClient.request(UserEndpoint.getUser(id: 123))
// Handle successful response
} catch let error as NetworkError {
switch error {
case .invalidURL:
// Handle invalid URL
case .invalidResponse:
// Handle invalid response format
case .httpError(let statusCode, let errorBody):
// Handle HTTP error with structured error response
print("Server error: \(statusCode), message: \(errorBody?.message ?? "Unknown")")
case .httpErrorData(let statusCode, let data):
// Handle HTTP error with raw data
case .decodingError(let error):
// Handle JSON decoding error
case .encodingError(let error):
// Handle JSON encoding error
case .unknownError(let error):
// Handle other errors
}
} catch {
// Handle unexpected errors
}NetSwift's protocol-based design makes it easy to mock for unit tests:
// Create a mock network session
class MockNetworkSession: NetworkSession {
var mockData: Data = Data()
var mockResponse: URLResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)!
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
return (mockData, mockResponse)
}
func upload(for request: URLRequest, from bodyData: Data) async throws -> (Data, URLResponse) {
return (mockData, mockResponse)
}
}
// Create a test helper
func createMockNetworkClient(jsonString: String, statusCode: Int = 200) -> NetworkClient {
let mockSession = MockNetworkSession()
mockSession.mockData = jsonString.data(using: .utf8)!
mockSession.mockResponse = HTTPURLResponse(url: URL(string: "https://example.com")!, statusCode: statusCode, httpVersion: nil, headerFields: nil)!
return NetworkClient(session: mockSession)
}
// Use in tests
func testUserFetch() async throws {
let mockJSON = """
{
"id": 123,
"name": "Test User",
"email": "[email protected]"
}
"""
let mockClient = createMockNetworkClient(jsonString: mockJSON)
let userService = UserService(networkClient: mockClient)
let user = try await userService.fetchUser(id: 123)
XCTAssertEqual(user.id, 123)
XCTAssertEqual(user.name, "Test User")
XCTAssertEqual(user.email, "[email protected]")
}- Swift 5.5+
- iOS 13.0+
- macOS 10.15+
- watchOS 6.0+
- tvOS 13.0+
NetSwift is available under the MIT license. See the LICENSE file for more info.
