Skip to content

freshOS/Networking

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Networking

Networking

Language: Swift 5 Platform: iOS 13+ SPM compatible License: MIT Build Status codebeat badge Release version

Networking brings together URLSession, async-await (or Combine ), Decodable and Generics to simplify connecting to a JSON api.

Demo time 🍿

Networking turns this:

let config = URLSessionConfiguration.default let urlSession = URLSession(configuration: config, delegate: nil, delegateQueue: nil) var urlRequest = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/users")!) urlRequest.httpMethod = "POST" urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") urlRequest.httpBody = "firstname=Alan&lastname=Turing".data(using: .utf8) let (data, _) = try await urlSession.data(for: urlRequest) let decoder = JSONDecoder() let user = try decoder.decode(User.self, from: data)

into:

let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") let user: User = try await network.post("/users", params: ["firstname" : "Alan", "lastname" : "Turing"])

Video tutorial

Alex from Rebeloper made a fantastic video tutorial, check it out here!

How

By providing a lightweight client that automates boilerplate code everyone has to write.
By exposing a delightfully simple api to get the job done simply, clearly, quickly.
Getting swift models from a JSON api is now a problem of the past

URLSession + Combine + Generics + Protocols = Networking.

What

  • Build a concise Api
  • Automatically map your models
  • Uses latest Apple's Combine / asyn-await
  • Compatible with native Decodable and any JSON Parser
  • Embarks a built-in network logger
  • Pure Swift, simple, lightweight & 0 dependencies

Getting Started

Install it

Networking is installed via the official Swift Package Manager.

Select Xcode>File> Swift Packages>Add Package Dependency...
and add https://github.com/freshOS/Networking.

Create a Client

let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com")

Make your first call

Use get, post, put, patch & delete methods on the client to make calls.

let data: Data = try await client.get("/posts/1")

All the apis are also available in Combine:

client.get("/posts/1").sink(receiveCompletion: { _ in }) { (data:Data) in // data }.store(in: &cancellables)

Get the type you want back

Networking recognizes the type you want back via type inference. Types supported are Void, Data, Any(JSON), Decodable(Your Model) & NetworkingJSONDecodable

This enables keeping a simple api while supporting many types :

let voidPublisher: AnyPublisher<Void, Error> = client.get("") let dataPublisher: AnyPublisher<Data, Error> = client.get("") let jsonPublisher: AnyPublisher<Any, Error> = client.get("") let postPublisher: AnyPublisher<Post, Error> = client.get("") let postsPublisher: AnyPublisher<[Post], Error> = client.get("")

Pass params

Simply pass a [String: CustomStringConvertible] dictionary to the params parameter.

let response: Data = try await client.posts("/posts/1", params: ["optin" : true ])

Parameters are .urlEncoded by default (Content-Type: application/x-www-form-urlencoded), to encode them as json (Content-Type: application/json), you need to set the client's parameterEncoding to .json as follows:

client.parameterEncoding = .json

Upload multipart data

For multipart calls (post/put), just pass a MultipartData struct to the multipartData parameter.

let params: [String: CustomStringConvertible] = [ "type_resource_id": 1, "title": photo.title] let multipartData = MultipartData(name: "file", fileData: photo.data, fileName: "photo.jpg", mimeType: "image/jpeg") client.post("/photos/upload", params: params, multipartData: multipartData).sink(receiveCompletion: { _ in }) { (data:Data?, progress: Progress) in if let data = data { print("upload is complete : \(data)") } else { print("progress: \(progress)") } }.store(in: &cancellables)

Add Headers

Headers are added via the headers property on the client.

client.headers["Authorization"] = "[mytoken]"

Add Timeout

Timeout (TimeInterval in seconds) is added via the optional timeout property on the client.

let client = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com", timeout: 15)

Alternatively,

client.timeout = 15 

Cancel a request

Since Networking uses the Combine framework. You just have to cancel the AnyCancellable returned by the sink call.

var cancellable = client.get("/posts/1").sink(receiveCompletion: { _ in }) { (json:Any) in print(json) }

Later ...

cancellable.cancel()

Log Network calls

3 log levels are supported: off, info, debug

client.logLevel = .debug

Handling errors

Errors can be handled on a Publisher, such as:

client.get("/posts/1").sink(receiveCompletion: { completion in switch completion { case .finished: break case .failure(let error): switch error { case let decodingError DecodingError: // handle JSON decoding errors case let networkingError NetworkingError: // handle NetworkingError // print(networkingError.status) // print(networkingError.code) default: // handle other error types print("\(error.localizedDescription)") } } }) { (response: Post) in // handle the response }.store(in: &cancellables)

Support JSON-to-Model parsing.

For a model to be parsable by Networking, it only needs to conform to the Decodable protocol.

If you are using a custom JSON parser, then you'll have to conform to NetworkingJSONDecodable.
For example if you are using Arrow for JSON Parsing. Supporting a Post model will look like this:

extension Post: NetworkingJSONDecodable { static func decode(_ json: Any) throws -> Post { var t = Post() if let arrowJSON = JSON(json) { t.deserialize(arrowJSON) } return t } }

Instead of doing it every models, you can actually do it once for all with a clever extension 🤓.

extension ArrowParsable where Self: NetworkingJSONDecodable { public static func decode(_ json: Any) throws -> Self { var t: Self = Self() if let arrowJSON = JSON(json) { t.deserialize(arrowJSON) } return t } } extension User: NetworkingJSONDecodable { } extension Photo: NetworkingJSONDecodable { } extension Video: NetworkingJSONDecodable { } // etc.

Design a clean api

In order to write a concise api, Networking provides the NetworkingService protocol. This will forward your calls to the underlying client so that your only have to write get("/route") instead of network.get("/route"), while this is overkill for tiny apis, it definitely keep things concise when working with massive apis.

Given an Article model

struct Article: DeCodable { let id: String let title: String let content: String }

Here is what a typical CRUD api would look like :

struct CRUDApi: NetworkingService { var network = NetworkingClient(baseURL: "https://my-api.com") // Create func create(article a: Article) async throws -> Article { try await post("/articles", params: ["title" : a.title, "content" : a.content]) } // Read func fetch(article a: Article) async throws -> Article { try await get("/articles/\(a.id)") } // Update func update(article a: Article) async throws -> Article { try await put("/articles/\(a.id)", params: ["title" : a.title, "content" : a.content]) } // Delete func delete(article a: Article) async throws { return try await delete("/articles/\(a.id)") } // List func articles() async throws -> [Article] { try await get("/articles") } }

The Combine equivalent would look like this:

struct CRUDApi: NetworkingService { var network = NetworkingClient(baseURL: "https://my-api.com") // Create func create(article a: Article) -> AnyPublisher<Article, Error> { post("/articles", params: ["title" : a.title, "content" : a.content]) } // Read func fetch(article a: Article) -> AnyPublisher<Article, Error> { get("/articles/\(a.id)") } // Update func update(article a: Article) -> AnyPublisher<Article, Error> { put("/articles/\(a.id)", params: ["title" : a.title, "content" : a.content]) } // Delete func delete(article a: Article) -> AnyPublisher<Void, Error> { delete("/articles/\(a.id)") } // List func articles() -> AnyPublisher<[Article], Error> { get("/articles") } }

About

⚡️ Concise networking code leveraging async-await, Decodable & Generics.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors 10

Languages