Another network wrapper for URLSession. Built to be simple, small and easy to create tests at the network layer of your application.

Overview

Build Status CocoaPods CocoaPods Carthage codecov codebeat badge Join at Telegram Linux Compatible


Another network wrapper for URLSession. Built to be simple, small and easy to create tests at the network layer of your application.

Install

Carthage

To integrate Frisbee into your Xcode project using Carthage, specify it in your Cartfile:

github "ronanrodrigo/Frisbee" ~> 0.2.5

Run carthage update to build the framework and drag the built Frisbee.framework into your Xcode project.

CocoaPods

To integrate Frisbee into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'Frisbee', '0.2.5'
end

Then, run the following command:

$ pod install

Swift Package Manager

To integrate Frisbee into your Swift Package Manager project, set the dependencies in your Package.swift:

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "<Your Packege Name>",
    dependencies: [
        .package(url: "https://github.com/ronanrodrigo/Frisbee.git", from: "0.2.5")
    ],
    targets: [
        .target(name: "<Your Packege Name>", dependencies: ["Frisbee"])
    ]
)

Usage

GET Request

Decodable Entity

A Response of a Request made in Frisbee will return an enum of Result<T>. Where T must be a decodable entity. In this guide it will be used a Movie entity like bellow.

struct Movie: Decodable {
    let name: String
}

Making a Request

You could abstract Frisbee usage in some class and inject an object that conforms to Getable protocol. So, in production ready code you will use an instance of NetworkGet object.

class MoviesController {
    private let getRequest: Getable

    // Expect something that conforms to Getable
    init(getRequest: Getable) {
        self.getRequest = getRequest
    }

    func listMovies() {
        getRequest.get(url: someUrl) { moviesResult: Result<[Movie]> in
            switch moviesResult {
                case let .success(movies): print(movies[0].name)
                case let .fail(error): print(error)
            }
        }
    }
}
// Who will call the MoviesController must inject a NetworkGet instance
MoviesController(getRequest: NetworkGet())

Query Parameters

It is easy to use query strings paramenters. Just create an Encodable struct and use it in get(url:query:onComplete:) method.

struct MovieQuery: Encodable {
    let page: Int
}
let query = MovieQuery(page: 10)
NetworkGet().get(url: url, query: query) { (result: Result<Movie>) in
    // ...
}

POST Request

Same way as GET request, Frisbee has a Postable protocol. And in prodution ready code you will use an instance of NetworkPost.

Making Request

It is the same logic as GET request.

class MoviesController {
    private let postRequest: Postable

    // Expect something that conforms to Postable
    init(postRequest: Postable) {
        self.postRequest = postRequest
    }

    func createMovie() {
        postRequest.post(url: someUrl) { moviesResult: Result<[Movie]> in
            switch moviesResult {
                case let .success(movies): print(movies[0].name)
                case let .fail(error): print(error)
            }
        }
    }
}

Body Arguments

It is easy to use body paramenters. Just create an Encodable struct and use it in post(url:body:onComplete:) method.

struct MovieBody: Encodable {
    let name: String
}
let body = MovieBody(name: "A New Movie")
NetworkPost().post(url: url, body: body) { (result: Result<Movie>) in
    // ...
}

Usage in Tests

In test target code you can create your own Getable (or Postable as you needed) mock.

public class MockGet: Getable {
    var decodableMock: Decodable!

    public func get<Entity: Decodable>(url: URL, completionHandler: @escaping (Result<Entity>) -> Void) {
        get(url: url.absoluteString, completionHandler: completionHandler)
    }

    public func get<Entity: Decodable>(url: String, completionHandler: @escaping (Result<Entity>) -> Void) {
        if let decodableMock = decodableMock as? Entity {
            completionHandler(.success(decodableMock))
        }
    }

}

And instead NetworkGet you will use to test the MockGet on MoviesController

class MoviesControllerTests: XCTestCase {
    func testDidTouchAtListMoviesWhenHasMoviesThenPresentAllMovies() {
        let mockGet = MockGet()
        let movies = [Movie(name: "Star Wars")]
        mockGet.decodableMock = movies
        let controller = MoviesController(getRequest: mockGet)

        controller.didTouchAtListMovies()

        XCTAssertEqual(controller.moviesQuantity, movies.count)
    }
}

Frisbee Next Features

  • Get request
  • Create Carthage distribution
  • Create Cocoapod distribution
  • Query parameter builder
  • Issue # 8 Implement other HTTP verbs
  • Issue # 7 Ready for use mock
  • Issue # 2 Propagate Swift Errors

Telegram

To collaborate, resolve questions and find out what's new about the Frisbee library, join the group on Telegram: https://t.me/FrisbeeLib

Comments
  • Equatables for Result and FrisbeeError

    Equatables for Result and FrisbeeError

    • Moved Equatable extensions from tests to framework
    • Moved Result get-properties extensions to framework
    • Add rest of cases on FrisbeeError: Equatable
    • Add == overload when Entity: Equatable on Result
    • Add Sequence.extensiveCombine on testing target to enable cartesian product of Sequences [0,1] x [a, b, c] = [(0,a), (0,b), (0,c), (1,a), (1,b), (1,c)]
    opened by williamhjcho 3
  • Add URL arguments on get methods

    Add URL arguments on get methods

    This allows URL to be build beforehand by someone else

    The String argument overloads should prefer/use URL overloads since they are already converted and ready for use

    opened by williamhjcho 2
  • Remove (url: String) methods from protocol requirements

    Remove (url: String) methods from protocol requirements

    Both Getable and Postable have methods that conveniently receives a url as String. The problem with this is that any object that adopts those protocols will have to add those methods as wells, and we can't be sure they will implement the right way - just validate the url and call the version with url: URl.

    We could just create extensions to the protocols with those convenient methods implemented like so:

    extension Getable {
        @discardableResult
        public func get<T: Decodable>(url: String, onComplete: @escaping OnComplete<T>) -> Cancellable {
            guard let url = URL(string: url) else {
                onComplete(.fail(FrisbeeError.invalidUrl))
                return NilCancellable()
            }
            return get(url: url, onComplete: onComplete)
        }
    }
    

    That way the client can still use URL as strings and we are sure it behaves correctly.

    opened by danielCarlosCE 1
  • Add JSON (de/en)coder to Adapter's initializers

    Add JSON (de/en)coder to Adapter's initializers

    JSON (de/en)coders can be configured with some other options, this just allows them to be inserted before initializing the adapters

    More work should be done on whoever uses them

    in progress 
    opened by williamhjcho 1
  • Add Data support

    Add Data support

    Until now it was possible to integrate just with JSON. Now it is possible to integrate with Data and with this is possible to download images from Data.

    bug 
    opened by ronanrodrigo 1
  • Create HTTP Post

    Create HTTP Post

    First part of issue #8 implementing POST method.

    • [x] Create a body builder (birl!);
    • [x] Create body builder tests;
    • [x] Create NetworkPost class;
    • [x] Create NetworkPostTests.
    opened by ronanrodrigo 1
  • Create init for error receiving an Error + Remove unknown error

    Create init for error receiving an Error + Remove unknown error

    Created an init for FrisbeError and remove some error handling in NetworkGet. With that is more easy to test error handling and .unknown error is unnecessary.

    opened by ronanrodrigo 1
  • Minor fixes at tests

    Minor fixes at tests

    • Update LinuxMain.swift
    • Remove unnecessary setup method at URLRequestFactoryTests
    • Extract test structs to files
    • Extract XCTest extension to file
    • Rename some tests
    opened by ronanrodrigo 1
  • Use same URLSession

    Use same URLSession

    For now, every time a new request is made with NetworkGetter.makeRequest, a new URLSession is used

    Allow injection of URLSession to enable use of delegates and queues

    bug 
    opened by williamhjcho 0
  • Possible to receive an empty body

    Possible to receive an empty body

    As mentioned at issue #8, it should be possible to accept an empty body, like when the server sends 204 (HTTP NOT CONTENT), it could be retrieved

    I did this due to some problems while I was working on DELETE HTTP VERB.

    opened by amadeu01 0
  • Why exist protocols Getable and Postable?

    Why exist protocols Getable and Postable?

    Postable and Postable are basically the same thing.

    public protocol Getable {
        @discardableResult
        func get<T: Decodable>(url: String,
                               onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func get<T: Decodable>(url: URL,
                               onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func get<T: Decodable, U: Encodable>(url: String, query: U,
                                             onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func get<T: Decodable, U: Encodable>(url: URL, query: U,
                                             onComplete: @escaping OnComplete<T>) -> Cancellable
    }
    
    
    
    protocol Postable {
        @discardableResult
        func post<T: Decodable>(url: String,
                                onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func post<T: Decodable>(url: URL,
                                onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func post<T: Decodable, U: Encodable>(url: String, body: U,
                                              onComplete: @escaping OnComplete<T>) -> Cancellable
        @discardableResult
        func post<T: Decodable, U: Encodable>(url: URL, body: U,
                                              onComplete: @escaping OnComplete<T>) -> Cancellable
    }
    
    

    Why do not make one protocol and then extend it if necessary?

    in discussion 
    opened by amadeu01 3
  • Replace closure callback with Promise/Future

    Replace closure callback with Promise/Future

    Why implement Promise/Future

    Promise<Value> enables more functionalities for chaining and perpetuating operations than a simple (Result<Value>) -> Void callback by allowing

    • Multiple transformations: map, flatMap
    • Error catching and treating
    • Easier operation workload dispatching to other threads/queues

    Basic snippet

    Inspired by RxSwift, Future (from fp paradigms) and Promise (from js):

    public final class Promise<Value> {
        let callback: (@escaping (PromiseEvent<Value>) -> Void) -> Void
    
        public init(_ callback: @escaping (@escaping (PromiseEvent<Value>) -> Void) -> Void) {
            self.callback = callback
        }
    
        public func work(_ callback: @escaping (PromiseEvent<Value>) -> Void) {
            self.callback(callback)
        }
    
        public func then(onFulfilled: ((Value) -> Void)? = nil,
                         onFailed: ((Error) -> Void)? = nil) {
            self.work { event in
                switch event {
                case .value(let value):
                    onFulfilled?(value)
                case .error(let error):
                    onFailed?(error)
                }
            }
        }
    
        public func map<T>(_ transform: @escaping (Value) throws -> T) -> Promise<T> {
            return Promise<T> { callback in
                self.work { event in callback(event.map(transform)) }
            }
        }
    
        public func flatMap<T>(_ transform: @escaping (Value) throws -> Promise<T>) -> Promise<T> {
            return Promise<T> { callback in
                self.work { event in
                    switch event {
                    case .value(let value):
                        do {
                            try transform(value).work(callback)
                        } catch {
                            callback(.error(error))
                        }
                    case .error(let error):
                        callback(.error(error))
                    }
                }
            }
        }
    }
    
    extension Promise {
        public convenience init(value: Value) {
            self.init { callback in callback(.value(value)) }
        }
    
        public convenience init(error: Error) {
            self.init { callback in callback(.error(error)) }
        }
    
        public static func just(_ value: Value) -> Promise<Value> {
            return Promise(value: value)
        }
    
        public static func error(_ error: Error) -> Promise<Value> {
            return Promise(error: error)
        }
    
        public static func empty() -> Promise<Void> {
            return Promise<Void>(value: ())
        }
    }
    

    Use examples

    Simple async dispatching

    // creates the Promise,
    let somePromise = Promise<Int> { callback in
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
            callback(.value(0))
        }
    }
    
    somePromise
        .map { $0 * $0 }
        // only starts the work when this function is called
        .work { event in
            switch event {
                case .value(let value): ...
                case .error(let error): ...
            }
         }
    
    // another way of listening to the result
    somePromise.then(onFulfilled: { value in ... }, 
                     onFailed: { error in ... })
    

    Other ways of creating Promises

    Promise.just(0)
    Promise<Value>.error(SomeError)
    Promise.empty() // of Void type
    

    Similarities

    Using RxSwift as a reference, a Promise is a Single Cold Observable. It contains some work which will only be executed when someone subscribes to it There should be ways of making them hot Observables, share subscriptions, combine with others (eg: zip) etc

    But differently from RxSwift, Promises do not send a stream of multiple objects, just zero or one (Value or Error) And it should be noted that it also doesn't have to deal with disposables and whatnot

    References

    RxSwift khanlou/Promise

    in discussion 
    opened by williamhjcho 4
  • Implement other HTTP verbs

    Implement other HTTP verbs

    Implement all (post, put, patch, delete) HTTP verbs. Creating other classes like NetworkGetter to keep it conforms to SOLID.

    • [x] POST #16
    • [x] PUT #27
    • [ ] DELETE
    feature in progress 
    opened by ronanrodrigo 1
  • Ready for use mock

    Ready for use mock

    It is nice to empower anyone to build their own mocks. But, I think that Frisbee must provide a ready for use mock. Something like that:

    public class MockGetter<Entity: Decodable>: Getable {
        var resultOnComplete: Result<Entity>
        var didCallGet = false
    
        // ...
    
        public func get<Entity: Decodable>(url: String, onComplete: @escaping (Result<Entity>) -> Void) {
            didCallGet = true
            completionHandler(resultOnComplete)
        }
    }
    
    feature 
    opened by ronanrodrigo 0
Releases(0.2.5)
Owner
Ronan Rodrigo Nunes
The first contact with programming was in the 2000s, creating scripts on IRC. Worked with web back/front. Now develops with Swift on the @uber app.
Ronan Rodrigo Nunes
Easy and lightweight network layer for creating different set of network requests like GET, POST, PUT, DELETE customizable with coders conforming to TopLevelDecoder, TopLevelEncoder

Easy and lightweight network layer for creating different set of network requests like GET, POST, PUT, DELETE customizable with coders conforming to TopLevelDecoder, TopLevelEncoder

Igor 2 Sep 16, 2022
Write clean, concise and declarative network code relying on URLSession, with the power of RxSwift. Inspired by Retrofit.

ReactiveAPI Reactive API library allows you to write clean, concise and declarative network code, with the power of RxSwift and URLSession! iOS Demo A

Sky UK Ltd 79 Nov 19, 2022
Say goodbye to the Fat ugly singleton Network Manager with this Network Layer

MHNetwork Protocol Oriented Network Layer Aim to avoid having bloated singleton NetworkManager Philosophy the main philosophy behind MHNetwork is to h

Mohamed Emad Hegab 19 Nov 19, 2022
VFNetwork is a protocol-oriented network layer that will help you assemble your requests in just a few steps.

Simple, Fast and Easy. Introduction VFNetwork is a protocol-oriented network layer that will help you assemble your requests in just a few steps. How

Victor Freitas 4 Aug 22, 2022
NSURLSession network abstraction layer, using Codable and Decodable for response and Encodable for request. โš™๏ธ๐Ÿš€

SONetworking NSURLSession network abstraction layer, using Codable and Decodable for response and Encodable for request. Project Folder and File Struc

Ahmad AlSofi 4 Jan 28, 2022
iOS 15, MVVM, Async Await, Core Data, Abstract Network Layer, Repository & DAO design patterns, SwiftUI and Combine

iOS 15, MVVM, Async Await, Core Data, Abstract Network Layer, Repository & DAO design patterns, SwiftUI and Combine

Conrado Mateu Gisbert 18 Dec 23, 2022
A reactive library for using URLSession

Reactive wrapper for URLSession using Combine. At its core, the library consist of the NetworkServiceClient protocol along with a minimal implementation NetworkService.

MFB Technologies, Inc. 2 Nov 20, 2022
To practice URLSession to fetch json data from open weather API

โ›…๏ธ weatherApp-iOS-practice ?? ๊ธฐ๋Šฅ ์ƒ์„ธ ๋„์‹œ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๋ฉด ํ˜„์žฌ ๋‚ ์”จ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

Jacob Ko 0 Dec 18, 2021
GeminiProtocol - URLSession support for the Gemini Protocol

GeminiProtocol Network.Framework and URLSession support for the Gemini Protocol

Izzy 3 Feb 16, 2022
Dratini is a neat network abstraction layer.

Dratini Dratini is a neat network abstraction layer. If you are looking for a solution to make your network layer neat, Dratini is your choice. Dratin

Kevin Lin 37 Jan 29, 2022
Network abstraction layer written in Swift.

Moya 14.0.0 A Chinese version of this document can be found here. You're a smart developer. You probably use Alamofire to abstract away access to URLS

Moya 14.4k Jan 1, 2023
Elegant network abstraction layer in Swift.

Elegant network abstraction layer in Swift. ไธญๆ–‡ Design Features Requirements Communication Installation Usage Base Usage - Target - Request - Download

null 100 Dec 9, 2022
Lightweight network abstraction layer, written on top of Alamofire

TRON is a lightweight network abstraction layer, built on top of Alamofire. It can be used to dramatically simplify interacting with RESTful JSON web-

MLSDev 528 Dec 26, 2022
A generic network layer written in swift

SwiftyNet 1.0.0 A generic network layer written in swift. you can use it as an abstraction layer above Alamofire with generic returned types. Installa

Mohamed Salah Zidane 17 Oct 11, 2021
Generic Network Layer created using Swift.

Generic-Network-Layer_iOS Generic Network Layer created using URLSession. Networking is an essential element in app development, and you'll need API c

Shubham Kr. Singh 41 Dec 31, 2022
Alamofire network layer

NVNetworkRequest Alamofire network layer Installation Add this to your Package dependencies: dependencies: [ .package(url: "https://github.com/vin

Vinh Nguyen 0 Nov 19, 2021
Network abstraction layer written in Swift.

Moya 15.0.0 A Chinese version of this document can be found here. You're a smart developer. You probably use Alamofire to abstract away access to URLS

Moya 14.4k Jan 4, 2023
Async network layer with Combine

Version 1.0.10 Currently Available Platform Version iOS 12.0 tvOS 10.0 macOS 10.15 watchOS 3.0 macCatalyst 13.0 Hover is a Network layer which uses Ap

Onur Hรผseyin ร‡antay 124 Oct 23, 2022
Alamofire Network Layer written in swift 5 using the protocol oriented, combine, UIKit, MVVM.

CoreAPI-iOS This project Contains Alamofire Network layer Based on Protocol Oriented Concept and Combine Framework. It is created with UIKit, Alamofir

Mehran Kamalifard 27 Nov 11, 2022