iOS 【如何写出最简洁优雅的网络封装 Moya + RxSwift】
来源:互联网 发布:浅谈网络管理重要性 编辑:程序博客网 时间:2024/06/05 04:21
前言
Why Moya ?
Alamofire可能是iOS Swift中最常用的HTTP networking library,用Alamofire可以抽象出NSURLSession和其中很多繁琐的细节,让你可以很方便地写出类似"APIManager"这种专门管理网络请求的类。
我们可以看一些例子,例子中用的JSONPlaceholder是一个免费的测试用的REST API:
//GET requestlet postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"Alamofire.request(.GET, postEndpoint) .responseJSON { response in //do something with response }//POST requestlet postsEndpoint: String = "http://jsonplaceholder.typicode.com/posts"let newPost = ["title": "title", "body": "body", "userId": 1]Alamofire.request(.POST, postsEndpoint, parameters: newPost, encoding: .JSON) .responseJSON { response in //do something with response }
对于每个请求,你必须提供一个String类型的URL和一个HTTP请求方法,像.GET,如果你有很多请求需要完成,那会让代码显得不那么容易阅读,维护和测试。
解决这些问题的办法就是利用Swift enum的特性给Alamofire添加一个router,这就是Moya。
What is Moya ?
Moya是一个基于Alamofire的Networking library,并且添加了对于ReactiveCocoa和RxSwift的接口支持,大大简化了开发过程,是Reactive Functional Programming的网络层首选。
Github上的官方介绍罗列了Moya的一些特点:- 编译的时候会检查API endpoint
- 可以用枚举值清楚地定义很多endpoint
- 增加了stubResponse类型,大大方便了unit testing
正文
正文首先介绍如何使用 Moya,第二步为 Moya 添加 RxSwift, 然后再加入数据层的映射(Model Mapping),最后在这个简单的例子中加入MVVM。一步一步地循序渐进,希望对大家有帮助。
Moya
首先创建一个 enum
来枚举你所有的 API targets。你可以把所有关于这个API的信息放在这个枚举类型中。
enum MyAPI { case Show case Create(title: String, body: String, userId: Int)}
这个枚举类型用来在编译的阶段给每个target提供具体的信息,每个枚举的值必须有发送http request需要的基本参数,像url,method,parameters等等。这些要求被定义在一个叫做TargetType
的协议中,在使用过程过我们的枚举类型需要服从这个协议。通常我们把这一部分的代码写在枚举类型的扩展里。
extension MyAPI: TargetType { var baseURL: URL { 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 self { case .Show: return nil case .Create(let title, let body, let userId): return ["title": title, "body": body, "userId": userId] } } var sampleData: Data { switch self { case .Show: return "[{\\"userId\\": \\"1\\", \\"Title\\": \\"Title String\\", \\"Body\\": \\"Body String\\"}]".data(using: String.Encoding.utf8)! case .Create(_, _, _): return "Create post successfully".data(using: String.Encoding.utf8)! } } var task: Task { return .request }}
Moya的使用非常简单,通过TargetType协议定义好每个target之后,就可以直接使用Moya开始发送网络请求了。
let provider = MoyaProvider<MyAPI>() provider.request(.Show) { result in // do something with result }
+ RxSwift
Moya本身已经是一个使用起来非常方便,能够写出非常简洁优雅的代码的网络封装库,但是让Moya变得更加强大的原因之一还因为它对于Functional Reactive Programming的扩展,具体说就是对于RxSwift和ReactiveCocoa的扩展,通过与这两个库的结合,能让Moya变得更加强大。我选择RxSwift的原因有两个,一个是RxSwift的库相对来说比较轻量级,语法更新相对来说比较少,我之前用过ReactiveCocoa,一些大版本的更新需求重写很多代码,第二个更重要的原因是因为RxSwift背后有整个ReactiveX的支持,里面包括Java,JS,.Net, Swift,Scala,它们内部都用了ReactiveX的逻辑思想,这意味着你一旦学会了其中的一个,以后可以很快的上手ReactiveX中的其他语言。
在我之前的几篇文章中已经写了RxSwift的一些简单上手的教程,不太熟悉RxSwift的朋友大家可以看一看,有个大致的了解。Moya提供了非常方面的RxSwift扩展:
let provider = RxMoyaProvider<MyAPI>()provider.request(.Show) .filterSuccessfulStatusCodes() .mapJSON() .subscribe(onNext: { (json) in //do something with posts print(json) }) .addDisposableTo(disposeBag)
- RxMoyaProvider是MoyaProvider的子类,是对RxSwift的扩展
filterSuccessfulStatusCodes()
是Moya为RxSwift提供的扩展方法,顾名思义,可以得到成功成功地网络请求,忽略其他的mapJSON()
也是Moya RxSwift的扩展方法,可以把返回的数据解析成 JSON 格式subscribe
是一个RxSwift的方法,对经过一层一层处理的 Observable 订阅一个onNext
的 observer,一旦得到 JSON 格式的数据,就会经行相应的处理addDisposableTo(disposeBag)
是 RxSwift 的一个自动内存处理机制,跟 ARC 有点类似,会自动清理不需要的对象。
运行程序,我们会得到下列的数据,网络请求的代码原来可以写得如此简洁优雅:
[ { "userId": 1, "id": 1, "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, "id": 2, "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, "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut", "body": "et iusto sed quo iure\\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
在实际应用过程中网络请求往往紧密连接着数据层(Model),具体地说,在我们的这个例子中,一般我们需要建立一个 Post
类用来统一管理数据,类里面有id
, title
, body
等信息,然后把得到的一个个 post 的 JSON 数据映射到Post
类,也就是数据层(Model)。
我之前最常用 SwiftyJSON
这个库来提取 JSON 中的各种信息,它是 Swift 中最常用的处理 JSON 的第三方库,但是在更新到了 Xcode 8 和 Swift 3 之后,这个库一直都没有更新,所以我使用了另一个 Github 上也有数千个star的库,叫做ObjectMapper。
利用 ObjectMapper
,创建 Post
类:
class Post: Mappable { var id: Int? var title: 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 <- map["userId"] }}
详细的 ObjectMapper 教程可以查看它的 Github 主页,我在这里只做简单的介绍。
使用 ObjectMapper
,需要让自己的 Model 类使用 Mappable 协议,这个协议包括两个方法:
required init?(map: Map) {}func mapping(map: Map) {}
在 mapping
方法中,用 <-
操作符来处理和映射你的 JSON 数据。
数据类建立好之后,我们还需要为 RxSwift 中的 Observable 写一个简单的扩展方法 mapObject
,利用我们写好的Post
类,一步就把 JSON 数据映射成一个个 post。
可以创建一个名为 Observable+ObjectMapper.swift
的文件:
import Foundationimport RxSwiftimport Moyaimport ObjectMapperextension Observable { func mapObject<T: Mappable>(type: T.Type) -> Observable<T> { return self.map { response in //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]> { return self.map { response in //if response is an array of dictionaries, then use ObjectMapper to map the dictionary //if not, throw an error guard let array = response as? [Any] else { throw RxSwiftMoyaError.ParseJSONError } guard let dicts = array as? [[String: Any]] else { throw RxSwiftMoyaError.ParseJSONError } return Mapper<T>().mapArray(JSONArray: dicts)! } }}enum RxSwiftMoyaError: String { case ParseJSONError case OtherError}extension RxSwiftMoyaError: Swift.Error { }
mapObject
方法处理单个对象,mapArray
方法处理对象数组。- 如果传进来的数据
response
是一个dictionary
,那么就利用 ObjectMapper 的map
方法映射这些数据,这个方法会调用你之前在mapping
方法里面定义的逻辑。 - 如果
response
不是一个dictionary
, 那么就抛出一个错误。 - 在底部自定义了简单的 Error,继承了 Swift 的 Error 类,在实际应用过程中可以根据需要提供自己想要的 Error。
运行下面的程序:
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)
得到结果:
100Title 1
+ MVVM
MVVM(Model-View-ViewModel)可以把数据的处理逻辑放到 ViewModel 从而大大减轻了 ViewController 的负担,是 RxSwift 中最常用的架构逻辑。
这个例子中我们可以把从网络请求得到数据的步骤写到 ViewModel 文件里:
import Foundationimport RxSwiftimport Moyaclass 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) }
然后在 ViewController 中调用 ViewModel 的方法:
import UIKitimport RxSwiftclass 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(disposeBag) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. }}
文中这个例子的完整项目放在了Github,大家可以下载参考。
- iOS 【如何写出最简洁优雅的网络封装 Moya + RxSwift】
- 用 RxSwift + Moya 写出优雅的网络请求代码
- moya + RxSwift 进行网络请求
- 如何写出简洁、优雅、可维护的组件。
- 如何写出简洁、优雅、可维护的组件。
- 通过 Moya+RxSwift+Argo 完成网络请求
- moya+RxSwift+HandyJSON 学习
- 系统性学习Moya+Alamofire+RxSwift+ObjectMapper的配合使用
- RxSwift+Moya之项目实战
- 如何写出简洁优美的CSS Reset
- 如何写出优雅的java代码
- 如何写出优雅的java代码
- Android开发如何写出优雅的代码
- RxJava2 + Retrofit2 优雅简洁封装
- Retrofit + Rxjava网络层的优雅封装
- 代码之美 - 如何写出优雅的PHP代码
- 代码之美 - 如何写出优雅的PHP代码
- 代码之美 - 如何写出优雅的PHP代码
- Handler源码解析三------ MessageQueue源码
- Nodejs实现socket通信
- day11总结
- 第一行代码:随时随地退出程序
- 姚伟斌:阿里云CDN技术演变之路
- iOS 【如何写出最简洁优雅的网络封装 Moya + RxSwift】
- 全栈工程师移动开发实践━产品与技术框架规划
- Xlistview的上拉加载,下拉刷新
- 202. Happy Number
- javaIO流接口总结
- c++ 多态 虚函数 析构函数 抽象类
- C语言 程序在内存中的分布
- SpringMVC对Rest风格的支持和静态资源文件的引用
- Error running app: Instant Run requires 'Tools | Android | Enable ADB integration' to be enabled.