Giter VIP home page Giter VIP logo

moya's Introduction

Moya 15.0.0

CircleCI codecov.io Carthage compatible Accio supported CocoaPods compatible Swift Package Manager compatible

A Chinese version of this document can be found here.

You're a smart developer. You probably use Alamofire to abstract away access to URLSession and all those nasty details you don't really care about. But then, like lots of smart developers, you write ad hoc network abstraction layers. They are probably called "APIManager" or "NetworkModel", and they always end in tears.

Moya Overview

Ad hoc network layers are common in iOS apps. They're bad for a few reasons:

  • Makes it hard to write new apps ("where do I begin?")
  • Makes it hard to maintain existing apps ("oh my god, this mess...")
  • Makes it hard to write unit tests ("how do I do this again?")

So the basic idea of Moya is that we want some network abstraction layer that sufficiently encapsulates actually calling Alamofire directly. It should be simple enough that common things are easy, but comprehensive enough that complicated things are also easy.

If you use Alamofire to abstract away URLSession, why not use something to abstract away the nitty gritty of URLs, parameters, etc?

Some awesome features of Moya:

  • Compile-time checking for correct API endpoint accesses.
  • Lets you define a clear usage of different endpoints with associated enum values.
  • Treats test stubs as first-class citizens so unit testing is super-easy.

You can check out more about the project direction in the vision document.

Sample Projects

We have provided two sample projects in the repository. To use it download the repo, run carthage update to download the required libraries and open Moya.xcodeproj. You'll see two schemes: Basic and Multi-Target - select one and then build & run! Source files for these are in the Examples directory in project navigator. Have fun!

Project Status

This project is actively under development, and is being used in Artsy's auction app. We consider it ready for production use.

Installation

Moya version vs Swift version.

Below is a table that shows which version of Moya you should use for your Swift version.

Swift Moya RxMoya ReactiveMoya RxSwift ReactiveSwift Alamofire
>= 5.2 >= 15.0.0 >= 15.0.0 >= 15.0.0 6.X 6.X 5.X
5.X >= 14.0.0 >= 14.0.0 >= 14.0.0 5.X 6.X 5.X
5.X >= 13.0.0 >= 13.0.0 >= 13.0.0 4.X 5.X >= 4.1
4.X 9.0.0 - 12.0.1 10.0.0 - 12.0.1 9.0.0 - 12.0.1 4.X(>= 10.0.0) 4.X(>= 12.0.0) 4.1(>= 11.0.0)
3.X 8.0.0 - 8.0.5 8.0.0 - 8.0.5 8.0.0 - 8.0.5 - - -
2.3 7.0.2 - 7.0.4 7.0.2 - 7.0.4 7.0.2 - 7.0.4 - - -
2.2 <= 7.0.1 <= 7.0.1 <= 7.0.1 - - -

Note: If you are using Swift 4.2 in your project, but you are using Xcode 10.2, Moya 13 should work correctly even though we use Swift 5.0.

Upgrading to a new major version of Moya? Check out our migration guides.

Swift Package Manager

Note: Instructions below are for using SwiftPM without the Xcode UI. It's the easiest to go to your Project Settings -> Swift Packages and add Moya from there.

To integrate using Apple's Swift package manager, without Xcode integration, add the following as a dependency to your Package.swift:

.package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0"))

and then specify "Moya" as a dependency of the Target in which you wish to use Moya. If you want to use reactive extensions, add also "ReactiveMoya", "RxMoya" or "CombineMoya" as your target dependency respectively. Here's an example PackageDescription:

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "MyPackage",
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0"))
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: ["ReactiveMoya"])
    ]
)

Combine note: if you're using CombineMoya, make sure that you use Xcode 11.5.0 or later. With earlier versions of Xcode you will have to manually add Combine as a weakly linked framework to your application target.

Accio

Accio is a dependency manager based on SwiftPM which can build frameworks for iOS/macOS/tvOS/watchOS. Therefore the integration steps of Moya are exactly the same as described above. Once your Package.swift file is configured, run accio update instead of swift package update.

CocoaPods

For Moya, use the following entry in your Podfile:

pod 'Moya', '~> 15.0'

# or 

pod 'Moya/RxSwift', '~> 15.0'

# or

pod 'Moya/ReactiveSwift', '~> 15.0'

# or

pod 'Moya/Combine', '~> 15.0'

Then run pod install.

In any file you'd like to use Moya in, don't forget to import the framework with import Moya.

Carthage

Carthage users can point to this repository and use whichever generated framework they'd like, Moya, RxMoya, ReactiveMoya, or CombineMoya.

Make the following entry in your Cartfile:

github "Moya/Moya" ~> 15.0

Then run carthage update --use-xcframeworks.

If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage.

NOTE: At this time, Carthage does not provide a way to build only specific repository submodules. All submodules and their dependencies will be built with the above command. However, you don't need to copy frameworks you aren't using into your project. For instance, if you aren't using ReactiveSwift, feel free to delete that framework along with ReactiveMoya from the Carthage Build directory after carthage update completes. Or if you are using ReactiveSwift but not RxSwift or Combine, then RxMoya, RxTest, RxCocoa, CombineMoya etc. can safely be deleted.

Manually

  • 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 Alamofire & Moya as a git submodule by running the following commands:
$ git submodule add https://github.com/Alamofire/Alamofire.git
$ git submodule add https://github.com/Moya/Moya.git
  • Open the new Alamofire folder, and drag the Alamofire.xcodeproj into the Project Navigator of your application's Xcode project. Do the same with the Moya.xcodeproj in the Moya folder.

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

  • Verify that the deployment targets of the xcodeprojs match that of your application target in the Project Navigator.
  • 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 Alamofire.xcodeproj folders each with two different versions of the Alamofire.framework nested inside a Products folder.

It does not matter which Products folder you choose from, but it does matter whether you choose the top or bottom Alamofire.framework.

  • Select the top Alamofire.framework for iOS and the bottom one for macOS.

You can verify which one you selected by inspecting the build log for your project. The build target for Alamofire will be listed as either Alamofire iOS, Alamofire macOS, Alamofire tvOS or Alamofire watchOS.

  • Click on the + button under "Embedded Binaries" again and add the correct build target for Moya.

  • And that's it!

The three frameworks are 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.

Usage

After some setup, using Moya is really simple. You can access an API like this:

provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
    switch result {
    case let .success(moyaResponse):
        let data = moyaResponse.data
        let statusCode = moyaResponse.statusCode
        // do something with the response data or statusCode
    case let .failure(error):
        // this means there was a network failure - either the request
        // wasn't sent (connectivity), or no response was received (server
        // timed out).  If the server responds with a 4xx or 5xx error, that
        // will be sent as a ".success"-ful response.
    }
}

That's a basic example. Many API requests need parameters. Moya encodes these into the enum you use to access the endpoint, like this:

provider = MoyaProvider<GitHub>()
provider.request(.userProfile("ashfurrow")) { result in
    // do something with the result
}

No more typos in URLs. No more missing parameter values. No more messing with parameter encoding.

For more examples, see the documentation.

Reactive Extensions

Even cooler are the reactive extensions. Moya provides reactive extensions for ReactiveSwift, RxSwift, and Combine.

ReactiveSwift

ReactiveSwift extension provides both reactive.request(:callbackQueue:) and reactive.requestWithProgress(:callbackQueue:) methods that immediately return SignalProducers that you can start, bind, map, or whatever you want to do. To handle errors, for instance, we could do the following:

provider = MoyaProvider<GitHub>()
provider.reactive.request(.userProfile("ashfurrow")).start { event in
    switch event {
    case let .value(response):
        image = UIImage(data: response.data)
    case let .failed(error):
        print(error)
    default:
        break
    }
}

RxSwift

RxSwift extension also provide both rx.request(:callbackQueue:) and rx.requestWithProgress(:callbackQueue:) methods, but return type is different for both. In case of a normal rx.request(:callbackQueue), the return type is Single<Response> which emits either single element or an error. In case of a rx.requestWithProgress(:callbackQueue:), the return type is Observable<ProgressResponse>, since we may get multiple events from progress and one last event which is a response.

To handle errors, for instance, we could do the following:

provider = MoyaProvider<GitHub>()
provider.rx.request(.userProfile("ashfurrow")).subscribe { event in
    switch event {
    case let .success(response):
        image = UIImage(data: response.data)
    case let .error(error):
        print(error)
    }
}

In addition to the option of using signals instead of callback blocks, there are also a series of signal operators for RxSwift and ReactiveSwift 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.

Combine

Combine extension provides requestPublisher(:callbackQueue:) and requestWithProgressPublisher(:callbackQueue) returning AnyPublisher<Response, MoyaError> and AnyPublisher<ProgressResponse, MoyaError> respectively.

Here's an example of requestPublisher usage:

provider = MoyaProvider<GitHub>()
let cancellable = provider.requestPublisher(.userProfile("ashfurrow"))
    .sink(receiveCompletion: { completion in
        guard case let .failure(error) = completion else { return }

        print(error)
    }, receiveValue: { response in
        image = UIImage(data: response.data)
    })

Community Projects

Moya has a great community around it and some people have created some very helpful extensions.

Contributing

Hey! Do you like Moya? Awesome! We could actually really use your help!

Open source isn't just writing code. Moya could use your help with any of the following:

  • Finding (and reporting!) bugs.
  • New feature suggestions.
  • Answering questions on issues.
  • Documentation improvements.
  • Reviewing pull requests.
  • Helping to manage issue priorities.
  • Fixing bugs/new features.

If any of that sounds cool to you, send a pull request! After your first contribution, we will add you as a member to the repo so you can merge pull requests and help steer the ship 🚢 You can read more details about that in our contributor guidelines.

Moya's community has a tremendous positive energy, and the maintainers are committed to keeping things awesome. Like in the CocoaPods community, always assume positive intent. Even if a comment sounds mean-spirited, give the person the benefit of the doubt.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Adding new source files

If you add or remove a source file from Moya, a corresponding change needs to be made to the Moya.xcodeproj project at the root of this repository. This project is used for Carthage. Don't worry, you'll get an automated warning when submitting a pull request if you forget.

Help us improve Moya documentation

Whether you’re a core member or a user trying it out for the first time, you can make a valuable contribution to Moya by improving the documentation. Help us by:

  • Sending us feedback about something you thought was confusing or simply missing.
  • Suggesting better wording or ways of explaining certain topics.
  • Sending us a pull request via GitHub.
  • Improving the Chinese documentation.

License

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

moya's People

Contributors

andrewsb avatar aschuch avatar ashfurrow avatar basthomas avatar colinta avatar devxoul avatar ealeksandrov avatar edwardvalentini avatar esttorhe avatar f-meloni avatar freak4pc avatar jeehut avatar jrheaton avatar justinmakaila avatar leoneparise avatar lucianopalmeida avatar nanoxd avatar neonichu avatar ohkanghoon avatar orta avatar peagasilva avatar pedrovereza avatar pmairoldi avatar scottrhoyt avatar sd10 avatar sunshinejr avatar swizzlr avatar ufosky avatar wangshengjia avatar wattson12 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  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

moya's Issues

Custom ParameterEncodings

I have been working with this framework on iOS and am having a compile issue when adding custom parameterEncodings. I was wondering if you could help me out.

My EyeQEndPoints enum does not implement URLRequestConvertible which I believe is ok.

I declare the custom closure as follows:

let xmlParameterEncoding:(URLRequestConvertible, [String: AnyObject]?) -> (NSURLRequest, NSError?) = {(URLRequest, parameters) in {

//this is just to get things working
return (NSURLRequest.URLRequest, nil)
}

In the endpointsClosure, I set the custom closure:

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

The compiler throws an error on .Custom(xmlParameterEncoding) of

error: type 'URLRequestConvertible' does not conform to protocol 'URLRequestConvertible'
return Endpoint(URL: url(target), sampleResponse: EndpointSampleResponse.Success(200, target.sampleData), method: method, parameters: parameters, parameterEncoding: .Custom(xmlParameterEncoding))

Thanks for your help.

test network failures

Right now you can only stub successes, but if your view controller reacts to failures, there is no way to stub that.

Documentation

Sorely missing. Now that eidolon is at a 1.0, I should have enough real-world experience to document, in detail, the different use cases and features of Moya.

Swagger Spec -> Moya Target Generator

I may end up doing this in anger, so I'm dropping a note here in case anyone else is interested. Probably a separate project? It uses mustache templates so how hard can it be?!

Distribute to AppStore

I use this framework with CocoaPods, but I can't install my app on my device via OTA install (use Fabric).

And I try to submit to iTunes Connect, I got this message.

Dear developer,

We have discovered one or more issues with your recent delivery for "". To process your delivery, the following issues must be corrected:

This bundle is invalid - The file extension must be .zip.

Any idea?

Alamofire submodule will not update

Looks like it was added with ssh key for a submodule. I don't use ssh. Can it be added by http?

Warning: Permanently added the RSA host key for IP address '192.30.252.131' to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Clone of '[email protected]:Alamofire/Alamofire.git' into submodule path 'submodules/Alamofire' failed

Keep track of in-flight requests

This will be a key component after #1 is completed. For example, if a request is made to an in-flight request, we can return the promise or signal or whatever associated with that response.

Unable to use Moya with CocoaPods

Thanks for this great work, but I can't use that because when i run bundle install, it show following message

Bundler could not find compatible versions for gem "xcodeproj":
  In Gemfile:
    cocoapods (>= 0) ruby depends on
      xcodeproj (~> 0.20.2) ruby

    xcodeproj (0.20.1)

Use of RAC is Incorrect

So right now, we send the request when the request method is called. That is no the ReactiveCocoa way. We should be returning a multicasted signal that sends the request when it is first subscribed to, like this.

Sample App

As @orta suggested, it'd be awesome to have a sample application demonstrating real-world use of Moya with the GitHub API.

Compile-time safe way to restrict API endpoint calls

So this is a tough one that I need to figure out an abstraction for. The basic gist is as follows: You set up a MoyaProvider with a bunch of endpoints. Cool, right? Except as of this moment, you access the API like this:

let provider = MoyaProvider(endpoints: [
                    Endpoint(URL: "http://rdjpg.com/300/200/", sampleResponse: {
                        /* doesn't matter */
                    })
                ])

...

provider.request("http://rdjpg.com/300/200/", completion: { (object: AnyObject?) -> () in
    ...
})

Not cool – let's avoid stringly-typedness in this library.

Is there a way to provide a compile-time check that the endpoint actually exists? Can we set up some sort of enum or token or something that you pass in when you create the endpoint, and then reuse that token later on when calling the API? It needs to be something accessible by both whoever sets up the API at app launch, likely the app delegate, and anyone else who wants to access it (view controllers, network layers, whoever).

It's tricky since I want a clear separation of this library and the app code. Since each app has a different API, this abstraction – whatever it ends up being – needs to be something defined in app code, but is used by the library.

So I need a thing that someone using this library creates, uses to register the endpoint, then uses somewhere else in order to access the API. Any thoughts?

Logo

All the projects nowadays need a cool, edgy logo. Not a high priority, but when it comes to logos, I only trust the best.

Allow specification of parameter encoding

Alamofire supports four different parameter encodings. Basically, we just need to expose those options to the Endpoint class. I'd like to do that without exposing the internals of Alamofire itself, though.

Delay in stubs

It would be sweet if there were a way to randomly or deterministically add a short delay to stub request returns to make it a little more "real".

MoyaErrorDomain must be public

When choosing to use Moya as a submodule rather than copy its files into a project, MoyaErrorDomain is unusable outside Moya since it's private by default.

Provide non-ReactiveCocoa interface

The main interface can still use RACSignals, but we should have an extension for the MoyaProvider class that allows developers to opt for callback blocks.

Inline testing providers

right now a test suite doing some networking looks like:

    beforeSuite { () -> () in
        // Force provider to stub responses
        APIKeys.sharedKeys = APIKeys(key: "", secret: "")
        Provider.sharedProvider = Provider.StubbingProvider()
    }

    afterSuite { () -> () in
        // Reset provider
        APIKeys.sharedKeys = APIKeys()
        Provider.sharedProvider = Provider.DefaultProvider()
    }

    it("returns some data") {
        setDefaultsKeys(nil, nil)

        var called = false
        XAppRequest(.Auctions).subscribeNext({ (object) -> Void in
            called = true
        })

        expect(called).to(beTruthy())
    }

    ... etc

This is fine, and it goes and access pre-rendered stubs etc. I'd like some choice, and to allow making something like this (excusing broken syntax):

  let networkProvider = Provider.runtimeProvider()
  beforeSuite { () -> () in
      Provider.sharedProvider = networkProvider
  }

  afterSuite { () -> () in
      Provider.sharedProvider = Provider.DefaultProvider()
  }

  it("returns some data") {

      var called = false

      networkProvider.responseString(.Auctions) {
        return "{ 'foo':'bar' }"
      }

      XAppRequest(.Auctions).subscribeNext({ (object) -> Void in
          called = true
      })

      expect(called).to(beTruthy())
  }

with maybe the potential of returning strings, json objects and data?

NSURLSessions something something

This is a new API as of iOS 7, and to be honest, I haven't taken a look at it yet. It could be really useful or something. Let's find out.

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.