A networking library for Swift

Related tags

Networking Nikka
Overview

Build Status CocoaPods Compatible Platform

Nikka

Nikka is a super simple Swift HTTP networking library that comes with many extensions to make it modular and really powerful.

Installation

###Requirements

  • iOS 8.0+
  • Xcode 8.0+
  • Swift 3.0+

With CocoaPods

use_frameworks!

pod "Nikka"

Usage

Simple example:

In 99% of the cases your app will need to talk to an API, that API has a behavior and you would like to map that behavior to your app. To handle errors correctly for instance. Nikka has been designed with this in mind. It allows you to define a common behavior for an API, by defining a Provider. Here's a simple example of what it looks like:

import Nikka

//Define your provider
class MyProvider:HTTPProvider {
    var baseURL = URL(string:"https://my-website.com/api")!
}
...

//This will send a GET request to the endpoint https://my-website.com/api/me/friends
MyProvider().request(Route(path:"/me/friends")).responseJSON { (response:Response<Any>) in
    //Parse here the object as an array or dictionary            
}

What is great with Nikka, is that it's highly scalable, and modular. You can define your endpoints wherever you want. It's up to you, if you want them all in one file or if you prefer to split them among your different services. Here is a nice way of presenting your endpoints and using them:

//Endpoints relative to the user
extension Route {
    static let me       = Route(path:"/me", method: .get}
    static let friends  = Route(path:"/me/friends") //GET is the default method when it is not specified
    static let user     = {(id:String) in Route(path:"/user/\(id)")} //You can pass parameters by defining a closure that will return a Route
    static let login    = {(email:String, password:String) in Route(path:"/login", method:.post, params:["email":email, "password":password])}
}

...

//Then you can simply send a request by passing the route you defined above.
//This will send a POST request to the endpoint https://my-website.com/api/login with a json body `{"email":"[email protected]","password":"bar"}``
MyProvider().request(.login("[email protected]", "bar")).responseJSON { (response:Response<Any>) in
    //Parse here the object as an array or dictionary            
}

Routes

Route is the object that allows you to define an endpoint and how you should talk to it. It requires at least a path that defines where the request is sent. GET is the default method used. You can pass parameters and headers to be sent with the request. And finally you can define how the parameters should be encoded.

Examples

Here are a few examples of valid routes:

Route(path:"/me") //GET request to the relative path /me without any headers or parameters
Route(path:"/login", method:.post, params:["email":"[email protected]", "password":"qwerty12345"]) //POST request that use the default JSON encoding and will pass the the parameters in the request body.
Route(path:"/user/about", method:.put, params:["text":"Hey!!"], headers:["Authorization":"12345"], encoding:.form) //PUT request that sends its parameters using the form encoding

Nikka currently supports 3 types of encoding, which are json, form, and url.

  • json will encode your parameters in JSON and put them in the request body
  • form will encode your parameters as query parameters and put them in the request body
  • url will encode your parameters as query parameters and append them to the URL

Multipart

A Route also supports multipart form. Here's a simple way to upload a multipart image:

//First you define the Route that will take the image in parameter put it into a Multipart form
extension Route{
    static let uploadImage = {(image:UIImage) -> Route in
        var form = MultipartForm()
        form.append(data: UIImageJPEGRepresentation(image, 0.9)!, forKey: "image", fileName: "image.jpg")
        return Route(path:"/profile/picture", method:.post, multipartForm:form)
    }
}

...

//Then you just use the route as usual to send the request
let image = UIImage(named:"DSC_0025.JPG")!
MyProvider().request(.uploadImage(image)).uploadProgress { (sent, total) in
    print("upload progress: \(sent)/\(total)")
}.responseJSON { (json) in
    print("json: \(json)")
}

Provider

The Provider is a type that implements HTTPProvider and that will map the behavior of your API. It can be highly customized:

Additional parameters and headers

If your API requires some headers or parameters for every request, you can set them in the declaration of the provider.

class MyProvider:HTTPProvider {
    var baseURL = URL(string:"https://my-website.com/api")!
    var additionalHeaders = ["Locale":"en-US"]
    var additionalParameters = ["token":"d71106a0-dd44-4092-a72e"]
}

Validating a response

APIs have a lot of different ways of handling errors. Nikka allows you to create your own errors that propagate it through the app, if you get a response you don't expect.

Let's take for instance the Deezer API, that returns a HTTP code 200 when it cannot find a song with a given ID. In your basic provider, 200 doesn't throw an error, but content of the response can't be parsed either. Here's how to deal with it:

https://api.deezer.com/track/313555658769 will return:

{
"error": {
  "type":"DataException",
  "message":"no data",
  "code":800
  }
}

You should first define your own error that conforms to the NikkaError protocol:

struct DeezerError : NikkaError, Equatable{
    var description:String
    var code:Int

    init(code:Int, description:String) {
        self.code = code
        self.description = description
    }

    public static func ==(lhs: DeezerError, rhs: DeezerError) -> Bool {
        return lhs.code == rhs.code
    }
}

Then when declaring your provider, you can implement the validate method, that will be called when a response is received

class DeezerProvider:HTTPProvider {
    var baseURL = URL(string:"https://api.deezer.com")!

    func validate(response: HTTPURLResponse, data: Data, error: Error?) -> NikkaError? {
        let jsonError = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [String:Any]
        if let error = json?["error"] as? [String:Any], let code = error["code"] as? Int, let desc = error["message"] as? String {
            return DeezerError(code:code, description:desc)
        }
        return nil
    }
}

Then when sending your request, if deezer returns a HTTP code 200 but with a json error in its body. It will go through the validator and send you back the error.

let myProvider = DeezerProvider()

myProvider.request(Route(path:"/track/313555658769").responseJSON { (response:Response<Any>) in
    switch response.result{
       case .success(let value):
           print("success")
       case .failure(let error):
           print("error: \(error.description)")
    }
}

//This will print "error: no data"

Stopping on error

In some cases, it is useful to define a certain behavior when an error is encountered. For instance if you receive a HTTP 401 error you might want to terminate the user session. This can be done by implementing the shouldContinue method.

class MyProvider:HTTPProvider {
    var baseURL:URL { return URL(string:"https://my-website.com/api")!}

    func shouldContinue(with error: NikkaError) -> Bool {
        if let err = error as? NikkaError , err == NikkaError.http(401){
            print("should log out")
            return false
        }
        return true
    }
}

Without Provider

In some cases, it doesn't make sense to define a provider because you already have a full URL. You can use the Default Provider for that extent. It allows you to send a request by passing a route with its full path.

DefaultProvider.request(Route(path:"https://my-website.com/api/user/1")).responseJSON { (response:Response<Any>) in
    switch response.result{
    case .success(let json):
        print("json: \(json)")
    case .failure(let error):
        print("error: \(error)")
    }
}

Extensions

Nikka works very well with JSON, it currently supports the libraries below to parse your data.

By using one of those extensions, you'll be able to send a request and get your object right away:

MyProvider().request(Route(path:"/user/1234")).responseObject { (response:Response<User>) in
    //You can check the content of the response for a user
    let user = response.result.value //This is a User?

    //Or you can switch on the response result if you want to manage an error case
    switch response.result{
    case .success(let user):
        print("success: user name is \(user.lastName)")
    case .failure(let error):
        print("error: \(error)") //Will print an error, if the User cannot be parsed for instance.
    }
}

Additionally Nikka supports Futures and RxSwift with extensions that can be used with CocoaPods by adding this to your PodFile:

pod "Nikka/Futures"
pod "Nikka/Rx"

Note that when importing a module, the core dependency is automatically imported as well, so you don't need to have both one of the above and the Nikka single pod.

Futures

Futures come very handy in modern programming, it allows you to chain your requests neatly. The Futures module allows you to return a future when you send a request.

I would encourage you to use the Future module along with a JSON library mentioned above. It is more powerful. However if for some reason you you like to get a Future with a JSON object or with the data return by the request. You could do the following:

//With Data and HTTPURLResponse
let loginDataFuture:Future<(HTTPURLResponse,Data)> = myProvider.request(.login("[email protected]", "bar")).response()
loginDataFuture.onComplete { (result:Result<Any>) in
    switch result{
    case .success(let json):
        print("json: \(json)")
    case .failure(let error):
        print("error: \(error)")
    }
}

//With JSON
let loginJSONFuture:Future<Any> = myProvider.request(.login("[email protected]", "bar")).responseJSON()
loginJSONFuture.onComplete { (result:Result<(HTTPURLResponse, Data)>) in
    expectation.fulfill()
    switch result{
    case .success(let response, let data):
        print("response code was: \(response.statusCode)")
    case .failure(let error):
        print("error: \(error)")
    }
}

RxSwift

Even better, the Rx extension. Similarly to the Future extension, it will return Rx Observable that can be chained.

I would encourage you to use the Rx module along with a JSON library mentioned above. It is more powerful. However if for some reason you you like to get a Observable with a JSON object or with the data return by the request. You could do the following:

//With Data and HTTPURLResponse
let loginDataObservable:Observable<(HTTPURLResponse,Data)> = myProvider.request(.login("[email protected]", "bar")).response()
loginDataObservable.subscribe(onNext: { (response:(HTTPURLResponse, Data)) in
    print("response code was: \(response.0.statusCode)")
}).addDisposableTo(bag)

//With JSON
let loginJSONObservable:Observable<Any> = myProvider.request(.login("[email protected]", "bar")).responseJSON()
loginJSONObservable.subscribe(onNext: { json in
    print("json is: \(json)")
}).addDisposableTo(bag)

Contributing

Contributions are more than welcome. Feel free to submit a pull request to add a new feature or to add support for your favorite JSON library.

License

Nikka is maintained by Emilien Stremsdoerfer and released under the Apache 2.0 license. See LICENSE for details

You might also like...
A resource based, protocol oriented networking library designed for pure-SwiftUI applications.

Monarch 👑 - WIP A resource based, protocol oriented networking library designed for pure-SwiftUI applications. Features: Async/Await Resource Based P

Elegant HTTP Networking in Swift
Elegant HTTP Networking in Swift

Alamofire is an HTTP networking library written in Swift. Features Component Libraries Requirements Migration Guides Communication Installation Usage

Robust Swift networking for web APIs
Robust Swift networking for web APIs

Conduit Conduit is a session-based Swift HTTP networking and auth library. Within each session, requests are sent through a serial pipeline before bei

Versatile HTTP Networking in Swift
Versatile HTTP Networking in Swift

Net is a versatile HTTP networking library written in Swift. 🌟 Features URL / JSON / Property List Parameter Encoding Upload File / Data / Stream / M

A type-safe, high-level networking solution for Swift apps
A type-safe, high-level networking solution for Swift apps

What Type-safe network calls made easy Netswift offers an easy way to perform network calls in a structured and type-safe way. Why Networking in Swift

A Swift Multiplatform Single-threaded Non-blocking Web and Networking Framework
A Swift Multiplatform Single-threaded Non-blocking Web and Networking Framework

Serverside non-blocking IO in Swift Ask questions in our Slack channel! Lightning (formerly Edge) Node Lightning is an HTTP Server and TCP Client/Serv

Simple asynchronous HTTP networking class for Swift

YYHRequest YYHRequest is a simple and lightweight class for loading asynchronous HTTP requests in Swift. Built on NSURLConnection and NSOperationQueue

Easy HTTP Networking in Swift a NSURLSession wrapper with image caching support
Easy HTTP Networking in Swift a NSURLSession wrapper with image caching support

Networking was born out of the necessity of having a simple networking library that doesn't have crazy programming abstractions or uses the latest rea

Declarative and Reactive Networking for Swift.

Squid Squid is a declarative and reactive networking library for Swift. Developed for Swift 5, it aims to make use of the latest language features. Th

Comments
  • Cannot get Nikka to work with Gloss.

    Cannot get Nikka to work with Gloss.

    Hi.

    Im trying to follow the documentation to get Nikka to work with Glossbut i cannot get it working.

    Documentation says:

    " To install this extension with CocoaPods simply add this line to your podfile:

    pod 'StreemNetworking/Gloss' "

    As "pod 'StreemNetworking/Gloss'" just return a unknown repo error, i changed that to pod 'Nikka/Gloss' and my pod file now looks like this:

    " Uncomment the next line to define a global platform for your project source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks!

    target 'xxxxx' do Pods for xxxxx pod 'IQKeyboardManagerSwift' pod 'Nikka' pod 'Nikka/Gloss' pod 'SideMenu', '~> 3.1.5' pod 'LFLoginController' pod 'SVProgressHUD' end "

    Running this gives me:

    " Analyzing dependencies Downloading dependencies Using Gloss (2.0.0) Using IQKeyboardManagerSwift (6.0.2) Using LFLoginController (0.3.1) Using Nikka (1.0.7) Using SVProgressHUD (2.2.5) Using SideMenu (3.1.5) Generating Pods project Integrating client project Sending stats Pod installation complete! There are 6 dependencies from the Podfile and 6 total pods installed. MortenHlsensMBP:iOS_Wisix mortenhjortshjnielsen$ "

    So it only installs Nikka 1.0.7, which gives me this compile error in the project:

    "Ambiguous reference to member 'fill(result:)'" Probaly because it wont fetch the newest version, i have tried expit to set version 2.1.1 of nikka but just get an error.

    Please help me move forward, as i have been working with this some hours now and cant move forward.

    Thank you in advance.

    Best Regards Morten

    opened by MortenHN81 1
Owner
Emilien Stremsdoerfer
Emilien Stremsdoerfer
ServiceData is an HTTP networking library written in Swift which can download different types of data.

ServiceData Package Description : ServiceData is an HTTP networking library written in Swift which can download different types of data. Features List

Mubarak Alseif 0 Nov 11, 2021
A networking library for Swift

Nikka Nikka is a super simple Swift HTTP networking library that comes with many extensions to make it modular and really powerful. Installation Usage

Emilien Stremsdoerfer 28 Jul 25, 2022
AsyncHTTP - Generic networking library written using Swift async/await

Generic networking library written using Swift async/await

Laszlo Teveli 7 Aug 3, 2022
Malibu is a networking library built on promises

Description Palm trees, coral reefs and breaking waves. Welcome to the surf club Malibu, a networking library built on promises. It's more than just a

Vadym Markov 409 Sep 9, 2022
Asynchronous socket networking library for Mac and iOS

CocoaAsyncSocket CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for macOS, iOS, and tvOS. The classes are described

Robbie Hanson 12.3k Sep 25, 2022
foursquare iOS networking library

FSNetworking foursquare's iOS networking library FSN is a small library for HTTP networking on iOS. It comprises a single class, FSNConnection, and se

Foursquare 386 Sep 1, 2022
A networking library for iOS, macOS, watchOS and tvOS

Thunder Request Thunder Request is a Framework used to simplify making http and https web requests. Installation Setting up your app to use ThunderBas

3 SIDED CUBE 15 Aug 25, 2022
QwikHttp is a robust, yet lightweight and simple to use HTTP networking library for iOS, tvOS and watchOS

QwikHttp is a robust, yet lightweight and simple to use HTTP networking library. It allows you to customize every aspect of your http requests within a single line of code, using a Builder style syntax to keep your code super clean.

Logan Sease 2 Mar 20, 2022
Malibu is a networking library built on promises

Description Palm trees, coral reefs and breaking waves. Welcome to the surf club Malibu, a networking library built on promises. It's more than just a

HyperRedink 10 Jan 29, 2022
Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable

Swish Nothing but net(working). Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable. It is protoco

thoughtbot, inc. 370 Sep 9, 2022