Giter Site home page Giter Site logo

moya's Introduction

Build Status

Moya Logo

So the basic idea is that we want some network abstraction layer to satisfy all of the requirements listed here. Mainly:

  • Treat test stubs as first-class citizens.
  • Only endpoints clearly defined through Moya can be accessed through Moya, enforced by the compiler.
  • Allow iterating through all potential API requests at runtime for API sanity checks.
  • Keep track of inflight requests and don't support duplicates.

Super-cool. We actually go a bit farther than other network libraries by abstracting the API endpoints away, too. Instead of dealing with endpoints (URLS) directly, we use targets, which represent actions to be take on the API. This is beneficial if one action can hit different endpoints; for example, if you want to GET a user profile, but the endpoint differs depending on if that user is a friend or not. Hey – I don't write these APIs, I just use 'em.

Sample Project

There's a sample project in the Sample directory. Make sure to run the installation instructions below, since it relies on the Alamofire submodule.

Project Status

Currently, we support Xcode 6.1.

This is nearing a 1.0 release, though it works now. We're using it in Artsy's new auction app.

Installation

This project has some dependencies, which are currently managed by a pre-release version of CocoaPods. If you don't want to run the pre-release version, that's ok! Just got to this previous commit and follow the instructions there.

If you want to use Moya with CocoaPods, use the Gemfile in this repo for your own project folder and run bundle install. Then add the following lines to your podfile:

pod 'Alamofire', :git => "https://github.com/mrackwitz/Alamofire.git", :branch => "podspec"
pod 'LlamaKit', :git => "https://github.com/AshFurrow/LlamaKit", :branch => "rac_podspec"
pod 'Moya', :git => "https://github.com/AshFurrow/Moya"

# Include the following only if you want to use ReactiveCocoa extensions with Moya
pod 'ReactiveCocoa', :git => "https://github.com/AshFurrow/ReactiveCocoa", :branch => "podspec"
pod 'Moya/Reactive', :git => "https://github.com/AshFurrow/Moya"

And finally run bundle exec pod install. Note the bundle exec – it is important!

Use

So how do you use this library? Well, it's pretty easy. Just follow this template. First, set up an enum with all of your API targets. Note that you can include information as part of your enum. Let's look at a simple example.

enum GitHub {
    case Zen
    case UserProfile(String)
}

This enum is used to make sure that you provide implementation details for each target (at compile time). The enum must conform to the MoyaTarget protocol, and by extension, the MoyaPath one as well. Let's take a look at what that might look like.

private extension String {
    var URLEscapedString: String {
        return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
    }
}

extension GitHub : MoyaPath {
    var path: String {
        switch self {
        case .Zen:
            return "/zen"
        case .UserProfile(let name):
            return "/users/\(name.URLEscapedString)"
        }
    }
}

extension GitHub : MoyaTarget {
    var baseURL: NSURL { return NSURL(string: "https://api.github.com") }
    var sampleData: NSData {
        switch self {
        case .Zen:
            return "Half measures are as bad as nothing at all.".dataUsingEncoding(NSUTF8StringEncoding)!
        case .UserProfile(let name):
            return "{\"login\": \"\(name)\", \"id\": 100}".dataUsingEncoding(NSUTF8StringEncoding)!
        }
    }
}

(The String extension is just for convenience – you don't have to use it.)

You can see that the MoyaPath protocol translates each value of the enum into a relative URL, which can use values embedded in the enum. Super cool. The MoyaTarget specifies both a base URL for the API and the sample data for each enum value. The sample data are NSData instances, and could represent JSON, images, text, whatever you're expecting from that endpoint.

Next, we'll set up the endpoints for use with our API.

public func url(route: MoyaTarget) -> String {
    return route.baseURL.URLByAppendingPathComponent(route.path).absoluteString
}

let endpointsClosure = { (target: GitHub, method: Moya.Method, parameters: [String: AnyObject]) -> Endpoint<GitHub> in
    return Endpoint<GitHub>(URL: url(target), method: method, parameters: parameters, sampleResponse: .Success(200, target.sampleData))
}

The block you provide will be invoked every time an API call is to be made. Its responsibility is to return an Endpoint instance configured for use by Moya. The parameters parameter is passed into this block to allow you to configure the Endpoint instance – these parameters are not automatically passed onto the network request, so add them to the Endpoint if they should be. They could be some data internal to the app that help configure the Endpoint. In this example, though, they're just passed right through.

Most of the time, this closure is just a straight translation from target, method, and parameters, into an Endpoint instance. However, since it's a closure, it'll be executed at each invocation of the API, so you could do whatever you want. Say you want to test errors, too.

let failureEndpointsClosure = { (target: GitHub, method: Moya.Method, parameters: [String: AnyObject]) -> Endpoint<GitHub> in
    let sampleResponse = { () -> (EndpointSampleResponse) in
        if sendErrors {
            return .Error(404, NSError())
        } else {
            return .Success(200, target.sampleData)
        }
    }()
    return Endpoint<GitHub>(URL: url(target), method: method, parameters: parameters, sampleResponse: sampleResponse)
}

Notice that returning sample data is required. One of the key benefits of Moya is that it makes testing the app or running the app using stubbed responses for API calls really easy.

Great, now we're all set. Just need to create our provider.

// Tuck this away somewhere where it'll be visible to anyone who wants to use it
var provider: MoyaProvider<GitHub>!

// Create this instance at app launch
let provider = MoyaProvider(endpointsClosure: endpointsClosure)

Neato. Now how do we make a request?

provider.request(.Zen, completion: { (data, error) in
    if let data = data {
        // do something with the data
    }
})

The request method is given a GitHub value and, optionally, an HTTP method and parameters for the endpoint closure.

ReactiveCocoa Extensions

Even cooler are the ReactiveCocoa extensions. It immediately returns a
RACSignal that you can subscribe to our bind or map or whatever you want to do. To handle errors, for instance, we could do the following:

provider.request(.UserProfile("ashfurrow")).subscribeNext({ (object) -> Void in
    image = UIImage(data: object as? NSData)
}, error: { (error) -> Void in
    println(error)
})

In addition to the option of using signals instead of callback blocks, there are also a series of signal operators that will attempt to map the data received from the network response into either an image, some JSON, or a string, with mapImage(), mapJSON(), and mapString(), respectively. If the mapping is unsuccessful, you'll get an error on the signal. You also get handy methods for filtering out certain status codes. This means that you can place your code for handling API errors like 400's in the same places as code for handling invalid responses.

License

Moya is released under an MIT license. See LICENSE for more information.

moya's People

Contributors

ashfurrow avatar delebedev avatar interstateone avatar juhagman avatar lipka avatar neonichu avatar orta avatar powerje avatar thomvis avatar

Watchers

 avatar  avatar

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.