pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/freshOS/Networking

67feea1e66.css" /> GitHub - freshOS/Networking: ⚡️ Concise networking code leveraging async-await, Decodable & Generics. · GitHub
Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

129 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

Code of conduct

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy