How to write the most concise and elegant network package Moya + RxSwift

Preface


  • Why Moya? HTTP networking library Alamofire is probably the most commonly used iOS Swift, Alamofire NSURLSession and many of them can be abstracted from the tedious details, so you can easily write a similar “APIManager” this specialized management network request class.

We can look at some examples, and the JSONPlaceholder used in this example is a free REST API for testing:

//GET request let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1" Alamofire.request (.GET, postEndpoint).ResponseJSON response in //do something with {response} //POST request let postsEndpoint: String = "http:// jsonplaceholder.typicode.com/posts" let newPost = "title", "title", "body", "body", "userId": 1] Alamofire.request (.POST, postsEndpoint parameters:, newPost, encoding:).JSON.ResponseJSON in //do something with response {response}

For each request, you must provide a String URL and a HTTP request methods, like.GET, if you have a lot of requests to be completed, it will make the code is not so easy to read, maintenance and testing.
‘s solution to these problems is to use Swift enum features to add a router to Alamofire, which is Moya.

  • What is Moya? Moya is a Alamofire based Networking library, and added to ReactiveCocoa and RxSwift interface support, greatly simplifies the development process, is the Reactive Functional Programming network layer is preferred. The official introduction on
    Github lists some of the features of Moya:
  • API endpoint is checked at compile time
  • You can explicitly define a lot of endpoint with enumerated values
  • The addition of the stubResponse type greatly facilitates the unit testing

text


The text first describes how to use Moya, the second step to add RxSwift to Moya, and then to add Mapping (Model) to the data layer, and finally add MVVM to this simple example. Step by step, step by step, in the hope that everyone will help.

Moya

First, create a enum to enumerate all of your API targets. You can put all the information about this API in this enumeration type.

Enum, MyAPI, {case, Show, case, Create (title:, String, body:, String, userId:, Int)}

This enumeration type is used to provide specific information to each target at compile time. Each enumeration must have the basic parameters needed to send HTTP request, such as URL, method, parameters, and so on. These requirements are defined in a protocol called TargetType, and our enumeration type needs to obey this protocol when used. We usually write this part of the code in the extension of the enumerated type.

Extension MyAPI: TargetType baseURL: URL {var {return (URL string: http://jsonplaceholder.typicode.com var path: String)!} {{switch self case.Show: return "/posts" case.Create (_, _, _): return "/posts" var method: Moya.Method {switch}} self {case.Show: return.GET case.Create (_, _ _): return.POST, VAR parameters: [String: Any]}} {switch {case? Self.Show: return nil case.Create (let title, let body, let userId): Return: Title ["title", "body": body "userId": userId] var sampleData: {switch Data}} Self {case.Show: return userId// ":" [{// "/" 1// "/" Title// ": Title", / / String// / / Body// / / Body: "String//"}] ".Data (using: String.Encoding.utf8) case!.Create (_, _, _): return" Create post successfully "(using:.Data String.Encoding.utf8)!}} var {return}}.Request task: Task

The use of Moya is very simple. After you have defined each target through the TargetType protocol, you can start sending network requests directly using Moya.

Let provider = MoyaProvider< MyAPI> (provider.request) (.Show) result in / / do something with {result}

+ RxSwift

Moya itself is a very convenient to use, can write network package library is very simple and elegant code, but let Moya become one of the reasons is because it is more powerful for the Functional Reactive Programming extension, specifically the extensions to RxSwift and ReactiveCocoa, and by combining these two libraries, can let Moya become more powerful. I choose RxSwift for two reasons, one is the RxSwift of the library is relatively lightweight, update the grammar is relatively small, I used ReactiveCocoa before, some versions of the update needs to rewrite a lot of code, second of the most important reason is because the RxSwift back after the whole ReactiveX support, which includes Java JS,.Net, Swift, Scala, they have used the internal logic thought of ReactiveX, which means that once you learn one, other languages later can quickly get started in ReactiveX.

In my previous several articles, have written some simple RxSwift tutorial, not too familiar with RxSwift friends, we can look at, there is a general understanding. Moya provides a very wide range of RxSwift extensions:

Let provider = RxMoyaProvider< MyAPI> (provider.request) (.Show) (.FilterSuccessfulStatusCodes) (.MapJSON).Subscribe (onNext: (JSON) in //do something {with posts} print (JSON)).AddDisposableTo (disposeBag)
  1. RxMoyaProvider is a subclass of MoyaProvider that is an extension of RxSwift
  2. FilterSuccessfulStatusCodes () is the extension method provided by Moya for RxSwift. As its name implies, you can get successful and successful network requests and ignore the others
  3. MapJSON () is also the extension method of Moya RxSwift, which can parse the returned data into JSON format
  4. Subscribe is a RxSwift method that subscribes to a onNext observer of Observable, which is processed by layer upon layer, and gets the corresponding data once it gets the data in the JSON format
  5. AddDisposableTo (disposeBag) is an automatic memory processing mechanism for RxSwift, which is somewhat similar to ARC and automatically clean up objects that are not needed.

Running the program, we get the following data, the code for the network request can be written so succinctly and elegantly:

[{"userId": 1, 1: "Id", "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit//nsuscipit recusandae consequuntur expedita et cum//nreprehenderit molestiae ut ut quas totam//nnostrum rerum EST autem sunt REM eveniet architecto}, {" userId ": 1 2," Id "," title ":" qui EST esse "," body ":" EST rerum tempore vitae//nsequi Sint nihil reprehenderit Dolor beatae EA Dolores neque//nfugiat blanditiis voluptate Porro vel nihil molestiae ut reiciendis//nqui aperiam non debitis possimus qui neque Nisi nulla}, {"userId": 1, "Id": 3, the "title": "EA molestias quasi exercitationem repellat qui IPSA sit aut", "body": "et iusto sed quo IUR E//nvoluptatem occaecati omnis eligendi aut ad//nvoluptatem doloribus vel accusantium Quis pariatur//nmolestiae Porro eius odio et Labore et velit aut "}, {" userId ": 1," Id ": 4," title ":" eum et EST occaecati "," body ":" ullam et saepe reiciendis voluptatem adipisci//nsit AMET autem assumenda provident rerum culpa//nquis HIC commodi nesciunt REM tenetur doloremque ipsam iure//nquis sunt voluptatem rerum Illo velit "},...

+ Model Mapping

In the actual application process network requests are often in close connection with the data layer (Model), specifically, in our example, we need to create a Post class to unified data management, class ID, title, body and other information, and then get a post JSON data is mapped to the the Post class, which is the data layer (Model).

I had the most commonly used SwiftyJSON this library to extract various information in JSON, it is the most commonly used Swift JSON in the third party libraries, but after the update to Xcode 8 and Swift 3, the library has not been updated, so I use another Github has thousands of star library that is called ObjectMapper.

Using ObjectMapper to create the Post class:

Class Post: Mappable id: var title: {var Int? String? Var body: String? Var userId: Int? Required init? (map: Map) {func} mapping (map: Map) {ID < map["Id" title "< map[" title "body" < map["body" userId & lt; map["userId"]}}

ObjectMapper tutorial in detail, you can see its Github home page, I’m here to do only a brief introduction.

Using ObjectMapper, you need your own Model class to use the Mappable protocol, which includes two methods:

Required init? (map: Map) func mapping (map: Map) {} {}

In the mapping method, you use the &lt – – operator to process and map your JSON data.

After the data class is established, we also need to write a simple extension method for the RxSwift in mapObject. Observable uses our well written Post class to map JSON data into post.

You can create a file called Observable+ObjectMapper.swift:

Import Foundation import RxSwift import Moya import ObjectMapper extension Observable mapObject< T: {func; Mappable> (type: T.Type); -> Observable< T> self.map in {return {response //if response is a dictionary, then use ObjectMapper to map the dictionary //if not throw an error guard let dict response as [String: Any] else = {throw RxSwiftMoyaError.ParseJSONError? Return Mapper< T>}; (.Map) (JSON: dict)!}} func mapArray< T: Mappable> (type: T.Type); -> Observable< [T]> self.map in {return {response //if response is an array of dictionaries, then use ObjectMapper to map the Dictionar Y //if not, throw an error guard let array = response as? [Any] else RxSwiftMoyaError.ParseJSONError guard let dicts {throw} = array as? [[String: Any]] else RxSwiftMoyaError.ParseJSONError return Mapper< {throw}; T> (.MapArray) (JSONArray: dicts)!}}} enum {RxSwiftMoyaError: String case ParseJSONError case extension RxSwiftMoyaError: Swift.Error {OtherError}}
  1. The mapObject method processes individual objects, and the mapArray method handles the array of objects.
  2. If the incoming data response is a dictionary, then use the ObjectMapper’s map method to map the data, which calls the logic you defined earlier in the mapping method.
  3. If response is not a dictionary, then an error is thrown.
  4. At the bottom of the definition of a simple Error, inherited the Swift Error class, in the actual application process, you can provide the desired Error as needed.

Run the following program:

Let provider = RxMoyaProvider< MyAPI> (provider.request) (.Show) (.FilterSuccessfulStatusCodes) (.MapJSON).MapArray (type: Post.self).Subscribe (onNext: (posts: [Post] in) {//do something} with posts print (posts.count)).AddDisposableTo (disposeBag) provider.request (.Create (title: Title 1, body: Body 1, userId: 1) (.MapJSON).MapObject (type:) Post.self (.Subscribe) onNext: (post: Post) {in //do something with post print (post.title!)}).AddDisposableTo (disposeBag)

Get results:

100 Title 1

+ MVVM

MVVM (Model-View-ViewModel) can reduce the burden of ViewController by putting the processing logic of data into ViewModel, which is the most common architecture logic in RxSwift.

In this example, we can write the steps from the network request to the data to the ViewModel file:

Import Foundation import RxSwift import Moya class ViewModel private let {provider = RxMoyaProvider< MyAPI> (func) getPosts (->); Observable< [Post]> return {provider.request (.Show) (.FilterSuccessfulStatusCodes) (.MapJSON).MapArray (type: Post.self func createPost (title:)} String, body: String, userId: Int) -> Observable< Post> {return; provider.request (.Create (title: title, body: body, userId: userId)).MapJSON (.MapObject) (type: Post.self)}

Then call the ViewModel method in ViewController:

Import UIKit import RxSwift class ViewController: UIViewController {let disposeBag = DisposeBag (let) viewModel = ViewModel (override) func (viewDidLoad) {super.viewDidLoad (Do) any additional setup after loading / the view, typically from a nib. (viewModel.getPosts).Subscribe (onNext: (posts: [Post] in) {//do something with posts print (posts.count)}.AddDisposableTo (disposeBag) viewModel.createPost (title:) Title 1, body: Body 1, userId: 1 (.Subscribe) onNext: (post: Post in) {//do something with post print (post.title!)}.AddDisposableTo (disposeB) AG override func (didReceiveMemoryWarning))} {(super.didReceiveMemoryWarning) Dispose any resources that can / of be recreated.}}

In this article, this example of the complete project on the Github, we can download the reference.