Getting Started with Moya

Moya is functional networking library, built on top of Alamofire, that applies the best of Swift’s language and compiler features to your network requests. Its key insight is tying URL request-building logic to an enumerated type, both guaranteeing that you don’t miss anything (switch statements need to be exhaustive), and allowing you to cleanly factor code out of your main suite of public “call this endpoint” functions. On top of that, there are subspecs supporting both ReactiveCocoa and RxSwift, making it relatively easy to support functional reactive programming in your project.

What’s in the Box?

Target

At its heart, Moya revolves around a single protocol called MoyaTarget. This is the enumerated type alluded to earlier, and encompasses your base URL and paths, supplied parameters, and HTTP methods. Let’s use the Twitter API for this example, and call our implementation TwitterTarget.

public enum Target: MoyaTarget {
    case .HomeTimeline
    case .NewPost(text: String)

    var baseURL: NSURL { return NSURL(string: "https://api.twitter.com/1.1/")! }

    var path: String { 
        switch self {
            case .HomeTimeline:
                return "statuses/user_timeline.json"
            case .NewPost:
                return "statuses/update.json"
        } 
    }

    var method: Moya.Method {
        switch self {
            case .HomeTimeline:
                return .GET
            case .NewPost:
                return .POST
        }
    }

    var parameters: [String: AnyObject] {
        switch self {
            case .HomeTimeline:
                return [:]
            case .NewPost(let text):
                return ["status": text]
        }
    }

    var sampleData: NSData { return NSData() }  // We just need to return something here to fully implement the protocol
}

So for each of the protocol methods we can switch on self (enums ftw), using let bindings to pull out associated values where we need them. The only non-obvious method is sampleData — it’s related to testing, we’ll come back to it later.

Provider

In order to interact with our TwitterTarget type we’ll need an instance of MoyaProvider; its request method is the gateway between TwitterTarget and Twitter’s servers. While we’re at it, let’s write a public function to wrap all of this stuff up.

    let provider = MoyaProvider<TwitterTarget>()

    public func getTimeline() -> (tweets: [Tweet]?, error: ErrorType?) {
        provider.request(.HomeTimeline) { [unowned self] (data: NSData?, statusCode: Int?, response: NSURLResponse?, error: ErrorType?) in
            guard self.requestSucceeded(statusCode) else {
                let error = // some error
                return (nil, error)
            }

            guard let data = data,
            json = try? NSJSONSerialization(data: data, options: NSJSONReadingOptions(),
            jsonArray = json as? [[String: AnyObject]] else {
                let error = // some error
                return (nil, error)
            }

            let tweets = jsonArray.flatMap { Tweet(json: $0) }
            return (tweets, nil)  
        }
    }

Using Moya we’ve factored everything but the bare essentials out of our public getTimeline function. The resulting code is simple, safe, and easy to reason about.

Testing

An awesome feature of Moya is the ability to test our logic with canned API responses with just a couple of small changes to our code. First, let’s implement that sampleData method for real using the sample responses in Twitter’s API docs:

public enum Target: MoyaTarget {

    (...)

    var sampleData: NSData {
        switch self {
            case .HomeTimeline:
                let jsonStr = "[{\"coordinates\": null,\"truncated\": false,\"created_at\": \"Tue Aug 28 21:16:23 +0000 2012\",\"favorited\": false,\"id_str\": \"240558470661799936\",\"in_reply_to_user_id_str\": null,\"entities\": {\"urls\": [],\"hashtags\": [],\"user_mentions\": []},\"text\": ...]"    // truncated because massive
                if let data = jsonStr.dataUsingEncoding(NSUTF8StringEncoding) {
                    return data
                } else {
                    print("Couldn't serialize string")
                    return NSData()
                }

            case .NewPost:
                (...)
        }
    }
}

Now in order to use this in our tests, we’re going to need an instance of MoyaProvider that we’ve configured for testing:

    let testProvider = MoyaProvider<TwitterTarget>(endpointClosure: MoyaProvider.DefaultEndpointMapping, endpointResolver: MoyaProvider.DefaultEndpointResolution, stubBehavior: MoyaProvider.ImmediateStubbingBehaviour)

This is almost the default configuration we get with a new MoyaProvider, except for the stubBehavior parameter. We’re using it to tell the provider that we want to use the stubbed responses we specified above, and not actually call out to the network. Now we can write tests like this (using Quick and Nimble, of course):

    describe("Getting the user's home timeline") {
        it("Should have some tweets") {
            getTimeline() { (tweets, error) in
                expect(tweets).toNot(beNil())
            }
        }
    }

Where to go from Here?

Headers

In order to add custom headers (say, for authentication), you’re going to need to provide a value for the endpointClosure parameter of MoyaProvider.


let ourEndpointClosure = { (target: TwitterTarget) -> Endpoint in
        let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString
        let endpoint = Endpoint(URL: url, sampleResponse: .Success(200, {target.sampleData}), method: target.method, parameters: target.parameters)
        let headers = headersForTarget(target)
        return endpoint.endpointByAddingHTTPHeaderFields(headers)
}

let provider = MoyaProvider<TwitterTarget>(endpointClosure: ourEndpointClosure)

Functional Reactive Programming

If you’d rather use signals than completion closures you can still use everything we’ve done so far, only swapping the base MoyaProvider out with RxMoyaProvider (for RxSwift) or ReactiveCocoaMoyaProvider (for Reactive Cocoa). Now the provider’s request method returns an Observable (or Signal) that you can map, filter, and bind to stuff.

Happy coding!