GoodNetworking is a powerful Swift library designed to simplify HTTP networking by leveraging the capabilities of Swift’s concurrency model, Combine, and Alamofire. It provides a flexible and easy-to-use API for handling complex network operations, making it easier to perform tasks such as sending requests, downloading, and uploading data.
Key Features
• NetworkSession: A powerful and flexible mechanism for managing HTTP sessions, providing built-in support for sending requests, downloading, and uploading files. It uses a session provider to handle session configuration and lifecycle management.
• Request Validation: Support for validating responses using custom validation providers.
• Flexible Endpoint Handling: Define your own endpoint configurations with ease, allowing for a clean and maintainable way to manage network requests.
Check out GoodNetworking documentation here
Create a Package.swift file and add the package dependency into the dependencies list.
Or to integrate without package.swift add it through the Xcode add package interface.
import PackageDescription
letpackage=Package(
name:"SampleProject",
dependencies:[.package(url:"https://github.com/GoodRequest/GoodNetworking" from:"addVersion")]
targets:[.target(
name:"Sample Target",
dependencies:[.product(name:"GoodNetworking",package:"GoodNetworking"),],),])Create an session actor best handled with dependency injection pattern
extensionNetworkSession{staticvarsampleSession: NetworkSession(baseUrl:"https://reqres.in/api")
}Create a sample endpoint
import GoodNetworking
enumSampleEndpoint:Endpoint{case singleUser(id:Int)varpath:String{switchself{case.singleUser(let id):"users/\(id)"}}Create a viewmodel where you call the async function to fetch the user.
@MainActorfinalclassUserProfileViewModel{varuserProfile:Result<User,Error>!func fetchUser()async{do{letresult:User=tryawaitNetworkSession.sampleSession.request(endpoint:SampleEndpoint.singleUser(id:1))
userProfile =.success(result)}catch{
userProfile =.failure(error)}}}Ofcourse the is much more
Our session works with alamofire session wrapping it easy to use.
There are 3 different initializers for our session
You can initilize with the parameter baseUrlProvider comforming to BaseUrlProviding protocol serving as baseUrl to your API calls where String is overloaded comform to it so any string baseUrl will work, but you can also provide more intricate logic that handles runtime baseUrl swapping.
The next parameter sessionProvider similarly to the first one as by default works with a DefaultSessionProvider that holds just a session with a default UrlSessionConfiguration so you can omit it, but you can also user DefaultSessionProvider's configuration init parameter and provide your own NetworkSessionConfiguration.
The other initializers serve as convenience to initializer these 2 values indirectly through other types.
Default
publicinit(
baseUrlProvider:BaseUrlProviding?=nil,
sessionProvider:NetworkSessionProviding=DefaultSessionProvider(configuration:.default)){self.baseUrlProvider = baseUrlProvider
self.sessionProvider = sessionProvider
}Backwards compatibility for our older projects
publicinit(
baseUrl:BaseUrlProviding?=nil,
configuration:NetworkSessionConfiguration=.default
){self.baseUrlProvider = baseUrl
self.sessionProvider =DefaultSessionProvider(configuration: configuration)}Customizable session if you need more than just the basic session parameters provided by NetworkSessionConfiguration
publicinit(
baseUrlProvider:BaseUrlProviding?=nil,
session:Alamofire.Session){self.baseUrlProvider = baseUrlProvider
self.sessionProvider =DefaultSessionProvider(session: session)}With this library you can directly call the request and update the state for a more straithforward way to update the UI
import Alamofire
import GoodNetworking
import SwiftUI
structUserScreen:View{@Stateprivatevaruser=Resource(session:.sampleSession, remote:RemoteUser.self)letuserId:Intvarbody:someView{ScrollView{VStack(spacing:24){userView(user: user.state)}.padding()}.refreshable{try?await user.read(forceReload:true)}.task{try?await user.read(request:UserRequest(id: userId))}}privatefunc loadingView()->someView{HStack(spacing:8){ProgressView()Text("Loading...")}}@ViewBuilderprivatefunc userView(user:ResourceState<User,NetworkError>)->someView{switch user {case.idle:Text("Resource idle")case.loading:HStack(spacing:8){ProgressView()Text("Loading...")}case.failure(let e):Text(e.localizedDescription)case.available(let user):letfields=["ID","First name","Last name","Email"]letvalues=[String(user.id), user.firstName, user.lastName, user.email
]LazyVGrid(columns:[GridItem(),GridItem()]){ForEach(Array(zip(fields, values)), id: \.0){ field, value inText(field)Text(value)}Text("Avatar")AsyncImage(url: user.avatar).aspectRatio(1, contentMode:.fit)}default:Text("Unknown state")}}}And you define yourself a model
structUser:Codable{varid:Intvaremail:StringvarfirstName:StringvarlastName:Stringvaravatar:URL?}structUserRequest:Encodable{letid:Int}structUserResponse:Decodable{letdata:User}extensionUser:Placeholdable{staticletplaceholder:User=User(
id:0,
email:"empty@example.com",
firstName:"John",
lastName:"Apple",
avatar:nil)}structRemoteUser:Readable{typealiasResource=UsertypealiasReadRequest=UserRequesttypealiasReadResponse=UserResponsenonisolatedstaticfunc endpoint(_ request:ReadRequest)throws(NetworkError)->Endpoint{SampleEndpoint.singleUser(id: request.id)}nonisolatedstaticfunc request(from resource:Resource?)throws(NetworkError)->ReadRequest?{guardlet resource else{throw.missingLocalData }returnUserRequest(id: resource.id)}nonisolatedstaticfunc resource(from response:ReadResponse)throws(NetworkError)->Resource{
response.data
}}And voila, all is setup. You can update the user directly with a state variable. The library also support all the CRUD operations with protocols Creatable, Readable, Updatable, Deletable and also more like Listable for pagination of lists.
structSampleLogger:NetworkLogger{func logNetworkEvent(message:Any, level:LogLevel, fileName:String, lineNumber:Int){switch level {case.debug:print("[DEBUG] \(fileName):\(lineNumber) - \(message)")case.info:print("[INFO] \(fileName):\(lineNumber) - \(message)")case.warning:print("[WARNING] \(fileName):\(lineNumber) - \(message)")case.error:print("[ERROR] \(fileName):\(lineNumber) - \(message)")}}}NetworkSession.sampleSession =NetworkSession(
baseUrl: urlProvider,
configuration:.default(logger:SampleLogger()),
logger:SampleLogger())GoodNetworking is released under the MIT license. See LICENSE for details.
