Giter Club home page Giter Club logo

restofire's Introduction

Restofire: A Protocol Oriented Networking Abstraction Layer in Swift

Platforms License

Swift Package Manager Carthage compatible CocoaPods compatible

Travis

Join the chat at https://gitter.im/Restofire/Restofire Twitter

Restofire is a protocol oriented networking client for Alamofire.

Features

  • Global Configuration for host / headers / parameters etc
  • Group Configurations
  • Per Request Configuration
  • Authentication
  • Response Validations
  • Custom Response Serializers like JSONDecodable
  • Isolate Network Requests from ViewControllers
  • Auto retry based on URLError codes
  • Request eventually when network is reachable
  • NSOperations

Requirements

  • iOS 10.0+ / Mac OS X 10.12+ / tvOS 10.0+ / watchOS 3.0+
  • Xcode 10

Installation

Dependency Managers

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

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

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

pod 'Restofire', '~> 5.0'

Then, run the following command:

$ pod install
Carthage

Carthage is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

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

github "Restofire/Restofire" ~> 5.0
Swift Package Manager

To use Restofire as a Swift Package Manager package just add the following in your Package.swift file.

import PackageDescription

let package = Package(
    name: "HelloRestofire",
    dependencies: [
        .Package(url: "https://github.com/Restofire/Restofire.git", from: "5.0.0")
    ]
)

Manually

If you prefer not to use either of the aforementioned dependency managers, you can integrate Restofire into your project manually.

Git Submodules

  • Open up Terminal, cd into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
$ git init
  • Add Restofire as a git submodule by running the following command:
$ git submodule add https://github.com/Restofire/Restofire.git
$ git submodule update --init --recursive
  • Open the new Restofire folder, and drag the Restofire.xcodeproj into the Project Navigator of your application's Xcode project.

    It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.

  • Select the Restofire.xcodeproj in the Project Navigator and verify the deployment target matches that of your application target.

  • Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.

  • In the tab bar at the top of that window, open the "General" panel.

  • Click on the + button under the "Embedded Binaries" section.

  • You will see two different Restofire.xcodeproj folders each with two different versions of the Restofire.framework nested inside a Products folder.

    It does not matter which Products folder you choose from.

  • Select the Restofire.framework & Alamofire.framework.

  • And that's it!

The Restofire.framework is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.

Embeded Binaries

  • Download the latest release from https://github.com/Restofire/Restofire/releases
  • Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
  • In the tab bar at the top of that window, open the "General" panel.
  • Click on the + button under the "Embedded Binaries" section.
  • Add the downloaded Restofire.framework & Alamofire.framework.
  • And that's it!

Configurations

Three levels of configuration

  • Global Configuration – The global configuration will be applied to all the requests. These include values like scheme, host, version, headers, sessionManager, callbackQueue, maxRetryCount, waitsForConnectivity etc.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    Restofire.Configuration.default.host = "httpbin.org"
    Restofire.Retry.default.retryErrorCodes = [.requestTimedOut,.networkConnectionLost]

    return true
}
  • Group Configuration – The group configuration inherits all the values from the global configuration. It can be used to group requests that have same behaviour but are different from the global configuration. For instance, If you have more than one host or if your global configuration has default url session and some requests require you to use ephemeral URL session.
import Restofire

protocol ApiaryConfigurable: Configurable {}

extension ApiaryConfigurable {
    public var configuration: Configuration {
        var configuration = Configuration.default
        configuration.host = "private-07c21-rahulkatariya.apiary-mock.com"
        configuration.headers = ["Content-Type": "application/json"]
        return configuration
    }
}

protocol ApiaryRequestable: Requestable, ApiaryConfigurable {}

import Restofire

struct NoteResponseModel: Decodable {
    var id: Int16
    var title: String
}

struct NotesGETService: ApiaryRequestable {
    typealias Response = [NoteResponseModel]
    var path: String? = "notes"
}
  • Per Request Configuration – The request configuration inherits all the values from the group configuration or directly from the global configuration.
import Restofire

struct NoteRequestModel: Encodable {
    var title: String
}

struct NotePOSTService: Requestable {
    typealias Response = NoteResponseModel
    let host: String = "private-07c21-rahulkatariya.apiary-mock.com"
    let headers = ["Content-Type": "application/json"]
    let path: String? = "notes"
    let method: HTTPMethod = .post
    var parameters: Any?

    init(parameters: NoteRequestModel) {
        let data = try! JSONEncoder().encode(parameters)
        self.parameters = try! JSONSerialization.jsonObject(with: data, options: .allowFragments)
    }
}

Usage

Making a Request

Requestable gives completion handler to enable making requests and receive response.

import Restofire

class ViewController: UITableViewController {
    var notes: [NoteResponseModel]!
    var requestOp: RequestOperation<NotesGetAllService>!

    override func viewDidLoad() {
        super.viewDidLoad()
        // We want to cancel the request to save resources when the user pops the view controller.
        requestOp = NotesGetAllService().execute() {
            if let value = $0.result.value {
                self.notes = value
            }
        }
    }

    func postNote(title: String) {
        let noteRequestModel = NoteRequestModel(title: title)
        // We don't want to cancel the request even if user pops the view controller.
        NotePOSTService(parameters: noteRequestModel).execute()
    }

    deinit {
        requestOp.cancel()
    }
}

Isolating Network Requests from UIViewControllers

Requestable gives delegate methods to enable making requests from anywhere which you can use to store data in your cache.

import Restofire

struct NotesGetAllService: ApiaryRequestable {
    ...

    func request(_ request: RequestOperation<NotesGetAllService>, didCompleteWithValue value: [NoteResponseModel]) {
      // Here you can store the results into your cache and then listen for changes inside your view controller.
    }
}

Custom Response Serializers

  • Decodable

By adding the following snippet in your project, All Requestable associatedType Response as Decodable will be decoded with JSONDecoder.

import Restofire

extension Restofire.DataResponseSerializable where Response: Decodable {
    public var responseSerializer: DataResponseSerializer<Response> {
        return DataRequest.JSONDecodableResponseSerializer()
    }
}
  • JSON

By adding the following snippet in your project, All Requestable associatedType Response as Any will be decoded with NSJSONSerialization.

import Restofire

extension Restofire.DataResponseSerializable where Response == Any {
    public var responseSerializer: DataResponseSerializer<Response> {
        return DataRequest.jsonResponseSerializer()
    }
}

Wait for Internet Connectivity

Requestable gives you a property waitsForConnectivity which can be set to true. This will make the first request regardless of the internet connectivity. If the request fails due to .notConnectedToInternet, it will retry the request when internet connection is established.

struct PushTokenPutService: Requestable {

    typealias Response = Data
    ...
    var waitsForConnectivity: Bool = true

}

Contributing

Issues and pull requests are welcome!

Author

Rahul Katariya @rahulkatariya91

License

Restofire is released under the MIT license. See LICENSE for details.

restofire's People

Contributors

corabius avatar gitter-badger avatar justinjiadev avatar rahul0x24 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

restofire's Issues

Swift Package Manager installation does not work

First of all thanks for an awesome looking project. Can't wait to try it out! 🎉

What did you do?

  • Tried to install Restofire with Swift Package Manager - Swift 4.1.0 (swiftpm-14050)

What did you expect to happen?

  • Restofire and it's dependencies being downloaded and linked with my package

What happened instead?

  • A build error. More specifically, a manifest parse error of Restofire's Package.swift when running swift build:
$ RestofireTest swift build
Updating https://github.com/Restofire/Restofire.git
https://github.com/Restofire/Restofire.git @ 4.0.0: error: manifest parse error(s):
/var/folders/jf/jlrsnxnx5pz247ctyynyjlgm0000gn/T/TemporaryFile.8zoWzT.swift:34:70: error: type 'Version' has no member 'upToNextMajor'
        .Package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "4.5.0"))
                                                                     ^~~~~~~~~~~~~

My Findings 💡

upToNextMajor has been introduced in V4 of the package description API.

Interestingly, if I clone Restofire and run swift package tools-version it outputs 3.1.0.
This is the default version if you do not specify a tools version.

Restofire does specify // swift-tools-version:4.0 in it's Package.swift. However, this needs to be done in the first line of the file (which is not the case for Restofire).

The issue can probably be fixed by moving // swift-tools-version:4.0 from its current position in Package.swift to the top of the file.

👉 If it's ok with you, I'd be happy to try this out and submit a pull request if it works! 🤓 🙏

Restofire Environment

Restofire version: 4.0.0
Xcode version: Version 9.3 (9E145) (did not use Restofire in Xcode, though)
Swift version: Apple Swift version 4.1 (swiftlang-902.0.48 clang-902.0.37.1)
Platform(s) running Restofire: macOS
macOS version running Xcode: macOS HighSierra Version 10.13.3

Demo Project

Just set up an empty Swift Package and try to install Restofire:

$ mkdir RestofireTest
$ cd RestofireTest
$ swift package init

then edit Package.swift:

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "RestofireTest",
    products: [
        .library(name: "RestofireTest", targets: ["RestofireTest"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Restofire/Restofire.git", .upToNextMajor(from: "4.0.0"))
    ],
    targets: [
        .target(name: "RestofireTest", dependencies: ["Restofire"])
    ]
)

Chore: Usage Example with FlowCoordinators

Add an example of using Restofire feature Isolate Network Calls from View Controllers with Flow Coordinators to fire requests and View Controllers listen to NSFetchedResultsDelegate.

iOS 13 issue "resource exceeds maximum size"

What did you do?

Just perform any GET request. For example:
http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?steamids=76561198015933145&key=BBD52AB62CD9B4E067C060466F0E9AC9

What did you expect to happen?

it should return any json.

What happened instead?

The issue description:
https://forums.developer.apple.com/thread/123348
"resource exceeds maximum size"

Restofire Environment

Restofire version: 4.0.1
Xcode version: 11.2.1
Swift version: 5
Platform(s) running Restofire: iOS 13.2.2
macOS version running Xcode: 10.15

Demo Project

import UIKit
import Restofire

let apiKey = "BBD52AB62CD9B4E067C060466F0E9AC9"

struct FSUserInfoModel: Codable {
    struct User: Codable {
        let steamid: String
        let personaname: String
        let profileurl: String
        let avatar: String?
        let avatarmedium: String?
        let avatarfull: String?
        let personastate: Int
        let communityvisibilitystate: Int
        let profilestate: Int?
        let lastlogoff: TimeInterval?
        let commentpermission: Int?

        func bestAvatarImagePath() -> String? {
            return (avatarfull ?? avatarmedium ?? avatar)
        }
    }

    let info: User?

    enum NestedKeys: String, CodingKey {
        case response
        case players
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: NestedKeys.self)
        let response = try container.nestedContainer(keyedBy: NestedKeys.self, forKey: .response)
        let users = try response.decode([User].self, forKey: .players)
        info = users.first
    }
}

extension Restofire.DataResponseSerializable where Response: Decodable {
    public var responseSerializer: DataResponseSerializer<Response> {
        return DataRequest.JSONDecodableResponseSerializer()
    }
}

struct FDGetPlayerSummariesService: Requestable {
    typealias Response = FSUserInfoModel
    var path: String? = "ISteamUser/GetPlayerSummaries/v0002/?steamids=76561198015933145&key=BBD52AB62CD9B4E067C060466F0E9AC9"
    var parameters: Any?

    init(steamids: [String]) {
//        let steamidsString = steamids.joined(separator: ",")
//        parameters = ["steamids": steamidsString, "key": apiKey]
        parameters = [:]
    }

    func request(_ request: RequestOperation<FDGetPlayerSummariesService>, didCompleteWithValue value: [FSUserInfoModel]) {
        print()
    }

    func request(_ request: RequestOperation<Self>, didFailWithError error: Error) {
        print()
    }
}

class FDNetworkBox {
    static let shared = FDNetworkBox()

    init() {
        Restofire.Configuration.default.scheme = "http://"
        Restofire.Configuration.default.host = "api.steampowered.com"
        Restofire.Retry.default.retryErrorCodes = [.timedOut,.networkConnectionLost]
    }

    func sync() {
        FDGetPlayerSummariesService(steamids: ["76561198015933145"]).execute()
    }
}

NOTE: I have even hardcoded the path which works in browser and in other libraries like MHNetwork but doesn't work with Restofire

XCODE 9 UrlError.Code error

Error occurred - Type 'URLError.Code' does not conform to protocol 'Hashable' in struct Retry
public var retryErrorCodes: Set<URLError.Code> = [.timedOut,
.cannotFindHost,
.cannotConnectToHost,
.dnsLookupFailed,
.networkConnectionLost
]

Failed to serialize Requestable Model

fatal error: ResponseSerializer failed to serialize the response to Requestable Model of type Dictionary<String, Any>:Restofire/Sources/AlamofireUtils.swift, line 110

I took this error in JSON starts and ends with "[]" like arrays. g.e.
[ {
"name": ""
},
{
"name":""
}]

but it works perfectly in "{}" response. g.e.
{
"name":""
}

Is this problem related with Restofire or Alamofire?

edit: I tested with Alamofire's standart API, it works perfectly. I think the problem related with Restofire's itself. I sent email as complete response that fails to serialize.

Example Usage with ObjectMapper

Hi! Can I use with AlamofireObjectMapper or ObjectMapper? Could you show any example of implementation with Restofire?

Thanks in advance.

Broken example

Just downloaded your library and tried to run example (I prefer cocoa pods) - nothing works

Feature: Streamable Protocols

Add Alamofire.stream() as Protocols. I am not sure that if it makes sense to use as a protocol or its better to use Alamofire directly for streams.

Generics Example

If we have a response which is common to all the requests like

{
status: true,
error_message: "Some Error Message",
result: { id: 12345, name: "Aar Kay"}
}

Doesn't work with SPM

$ swift package fetch
Cloning https://github.com/Restofire/Restofire.git
HEAD is now at 885fdf5 Add pod trunk push script to automatically deploy to cocoapods
Resolved version: 2.1.0
Cloning https://github.com/Alamofire/Alamofire.git
HEAD is now at 668cc5a Added release notes to the CHANGELOG and bumped the version to 4.0.1.
Resolved version: 4.0.1
error: the package has an unsupported layout, unexpected source file(s) found: …/Packages/Alamofire-4.0.1/Tests/AFError+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/AuthenticationTests.swift, …/Packages/Alamofire-4.0.1/Tests/BaseTestCase.swift, …/Packages/Alamofire-4.0.1/Tests/CacheTests.swift, …/Packages/Alamofire-4.0.1/Tests/DownloadTests.swift, …/Packages/Alamofire-4.0.1/Tests/FileManager+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/MultipartFormDataTests.swift, …/Packages/Alamofire-4.0.1/Tests/NetworkReachabilityManagerTests.swift, …/Packages/Alamofire-4.0.1/Tests/ParameterEncodingTests.swift, …/Packages/Alamofire-4.0.1/Tests/RequestTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResponseSerializationTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResponseTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResultTests.swift, …/Packages/Alamofire-4.0.1/Tests/ServerTrustPolicyTests.swift, …/Packages/Alamofire-4.0.1/Tests/SessionDelegateTests.swift, …/Packages/Alamofire-4.0.1/Tests/SessionManagerTests.swift, …/Packages/Alamofire-4.0.1/Tests/String+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/TLSEvaluationTests.swift, …/Packages/Alamofire-4.0.1/Tests/URLProtocolTests.swift, …/Packages/Alamofire-4.0.1/Tests/UploadTests.swift, …/Packages/Alamofire-4.0.1/Tests/ValidationTests.swift
fix: move the file(s) inside a module

$ swift package generate-xcodeproj
error: the package has an unsupported layout, unexpected source file(s) found: …/Packages/Alamofire-4.0.1/Tests/AFError+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/AuthenticationTests.swift, …/Packages/Alamofire-4.0.1/Tests/BaseTestCase.swift, …/Packages/Alamofire-4.0.1/Tests/CacheTests.swift, …/Packages/Alamofire-4.0.1/Tests/DownloadTests.swift, …/Packages/Alamofire-4.0.1/Tests/FileManager+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/MultipartFormDataTests.swift, …/Packages/Alamofire-4.0.1/Tests/NetworkReachabilityManagerTests.swift, …/Packages/Alamofire-4.0.1/Tests/ParameterEncodingTests.swift, …/Packages/Alamofire-4.0.1/Tests/RequestTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResponseSerializationTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResponseTests.swift, …/Packages/Alamofire-4.0.1/Tests/ResultTests.swift, …/Packages/Alamofire-4.0.1/Tests/ServerTrustPolicyTests.swift, …/Packages/Alamofire-4.0.1/Tests/SessionDelegateTests.swift, …/Packages/Alamofire-4.0.1/Tests/SessionManagerTests.swift, …/Packages/Alamofire-4.0.1/Tests/String+AlamofireTests.swift, …/Packages/Alamofire-4.0.1/Tests/TLSEvaluationTests.swift, …/Packages/Alamofire-4.0.1/Tests/URLProtocolTests.swift, …/Packages/Alamofire-4.0.1/Tests/UploadTests.swift, …/Packages/Alamofire-4.0.1/Tests/ValidationTests.swift
fix: move the file(s) inside a module

$ swift --version
Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
Target: x86_64-apple-macosx10.9

Manual Installation

CocoaPods and Carthage are awesome tools and make our life really easier, but there are some devs who still don't know how to use them.

It would be cool to add the Manual installation guide in your README.md. You can take a look at my iOS Readme Template to see how you can do it.

Error in readme

What did you do?

Copied some code from readme.

What did you expect to happen?

The project is compiled

What happened instead?

self.path += path
Value of optional type 'String?' must be unwrapped to a value of type 'String'

you can't apply += to String? and String variables.

Restofire Environment

Restofire version: 4.0.1
Xcode version: 11.2.1
Swift version: 5
Platform(s) running Restofire: iOS
macOS version running Xcode: 10.15.1

Demo Project

import Foundation
import Restofire

protocol NYConfigurable: Configurable {}
protocol NYRequestable: Requestable, NYConfigurable {}

extension Restofire.DataResponseSerializable where Response: Decodable {
    public var responseSerializer: DataResponseSerializer<Response> {
        return DataRequest.JSONDecodableResponseSerializer()
    }
}

struct UserInfoGETService: NYRequestable {
    typealias Response = FSUserInfoModel
    var path: String?
    var parameters: Any?

    init(path: String, parameters: Any) {
        self.path += path
        self.parameters = parameters
    }
}

in general why +=? Maybe simply =?

Feature: Fake Responses

The idea is to create an environment variable or a launch argument which when set to true will use the fake responses if available.

This will remove the dependency of server while developing modules.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.