基于Moya、RxSwift和ObjectMapper优雅实现REST API请求
程序员文章站
2022-05-10 12:00:00
在Android开发中有非常强大的 Retrofit 请求,结合RxJava可以非常方便实现 RESTful API 网络请求。在 iOS开发中也有非常强大的网络请求库 "Moya" ,Moya是一个基于 Alamofire 开发的,轻量级的Swift网络层。Moya的可扩展性非常强,可以方便和RX ......
在android开发中有非常强大的 retrofit 请求,结合rxjava可以非常方便实现 restful api 网络请求。在 ios开发中也有非常强大的网络请求库 moya ,moya是一个基于 alamofire 开发的,轻量级的swift网络层。moya的可扩展性非常强,可以方便和rxswift、objectmapper结合。
测试 rest api 定义
我们先用服务端定义几个rest api,开发者根据自己的条件来实现。
请求错误格式实例
{ "error": "密码错误", "error_code": "password_error" }
测试 api 列表
- http://127.0.0.1:8080/account/login,参数username、password,post请求,成功响应为user。
- http://127.0.0.1:8080/user/{userid},get请求,成功响应为user。
- http://127.0.0.1:8080/user/query?q={keyword},get请求,成功响应为user列表。
创建接口
// myapiservice.swift import moya enum myapiservice { case login(username:string,password:string) case user(userid:string) case userquery(keyword:string) } extension myapiservice:targettype{ // 定义请求的host var baseurl: url { return url(string: "http://127.0.0.1:8080")! } // 定义请求的路径 var path: string { switch self { case .login(_, _): return "/account/login" case .user(let userid): return "user/\(userid)" case .userquery(_): return "user/query" } } // 定义接口请求方式 var method: moya.method { switch self { case .login: return .post case .user,.userquery: return .get } } // 定义模拟数据 var sampledata: data { switch self { case .login(let username, _): return "{\"username\": \"\(username)\", \"id\": 100}".data(using: string.encoding.utf8)! case .user(_): return "{\"username\": \"wiki\", \"id\": 100}".data(using: string.encoding.utf8)! case .userquery(_): return "{\"username\": \"wiki\", \"id\": 100}".data(using: string.encoding.utf8)! } } // 构建参数 var task: task { switch self { case .login(let username, let passowrd): return .requestparameters(parameters: ["username": username,"passowrd": passowrd], encoding: urlencoding.default) case .user(_): return .requestplain case .userquery(let keyword): return .requestparameters(parameters: ["keyword": keyword], encoding: urlencoding.default) } } // 构建请求头部 var headers: [string : string]? { return ["content-type": "application/json"] } }
请求数据
let provider = moyaprovider<myapiservice>() // moya 提供最原始的请求方式,响应的数据是二进制 provider.request(.user(userid: "101")){ result in // do something with the result let text = string(bytes: result.value!.data, encoding: .utf8) print("text1 = \(text)") } // 结合rxswift,响应的数据是二进制 provider.rx.request(.user(userid: "101")).subscribe({result in // do something with the result switch result { case let .success(response): let text = string(bytes: response.data, encoding: .utf8) print("text2 = \(text)") case let .error(error): print(error) } }) // 通过mapjson把数据转换成json格式 provider.rx.request(.user(userid: "101")).mapjson().subscribe({result in // do something with the result switch result { case let .success(text): print("text3 = \(text)") case let .error(error): print(error) } }) // 通过mapjson把数据转换成json格式,并转换成最常见的observable provider.rx.request(.user(userid: "101")).mapjson().asobservable().subscribe(onnext: { result in // do something with the result print("text4 = \(result)") }, onerror:{ error in // do something with the error })
请求数据:rxblocking
rxblocking使用教程 ,可以使用同步的方式请求网络
import rxblocking do{ let text = try provider.rx.request(.user(userid: "101")).mapjson().toblocking().first() print("text5 = \(text)") }catch{ print(error) }
结合 objectmapper
引入objectmapper
pod 'objectmapper', '~> 3.4'
编写rxswift拓展代码
// moyarxswiftobjectmapperextension.swift import foundation import rxswift import moya import objectmapper public extension primitivesequence where traittype == singletrait, elementtype == response { func mapobject<t: basemappable>(type: t.type) -> single<t> { return self.map{ response in return try response.mapobject(type: type) } } func maparray<t: basemappable>(type: t.type) -> single<[t]> { return self.map{ response in return try response.maparray(type: type) } } } public extension observabletype where e == response { func mapobject<t: basemappable>(type: t.type) -> observable<t> { return self.map{ response in return try response.mapobject(type: type) } } func maparray<t: basemappable>(type: t.type) -> observable<[t]> { return self.map{ response in return try response.maparray(type: type) } } } public extension response{ func mapobject<t: basemappable>(type: t.type) throws -> t{ let text = string(bytes: self.data, encoding: .utf8) if self.statuscode < 400 { return mapper<t>().map(jsonstring: text!)! } do{ let serviceerror = mapper<serviceerror>().map(jsonstring: text!) throw serviceerror! }catch{ if error is serviceerror { throw error } let serviceerror = serviceerror() serviceerror.message = "服务器开小差,请稍后重试" serviceerror.error_code = "parse_error" throw serviceerror } } func maparray<t: basemappable>(type: t.type) throws -> [t]{ let text = string(bytes: self.data, encoding: .utf8) if self.statuscode < 400 { return mapper<t>().maparray(jsonstring: text!)! } do{ let serviceerror = mapper<serviceerror>().map(jsonstring: text!) throw serviceerror! }catch{ if error is serviceerror { throw error } let serviceerror = serviceerror() serviceerror.message = "服务器开小差,请稍后重试" serviceerror.error_code = "parse_error" throw serviceerror } } } class serviceerror:error,mappable{ var message:string = "" var error_code:string = "" required init?(map: map) {} init() { } func mapping(map: map) { error_code <- map["error_code"] message <- map["error"] } var localizeddescription: string{ return message } }
创建 user 类
// user.swift import objectmapper class user: mappable { required init?(map: map) {} func mapping(map: map) { userid <- map["userid"] name <- map["name"] age <- map["age"] } var userid:int = 0 var name:string = "" var age:int = 0 }
测试
do{ let user = try provider.rx.request(.user(userid: "101")).mapobject(type: user.self).toblocking().first() print("user.name = \(user?.name)") }catch{ print(error) } do{ let user = try provider.rx.request(.user(userid: "101")).asobservable().mapobject(type: user.self).toblocking().first() print("user.name = \(user?.name)") }catch{ print(error) } do{ let users = try provider.rx.request(.userquery(keyword: "wiki")).maparray(type: user.self).toblocking().first() print("test8 users.count = \(users?.count)") }catch{ if error is serviceerror { print((error as! serviceerror).message) } print(error) }
打印日志
private func jsonresponsedataformatter(_ data: data) -> data { do { let dataasjson = try jsonserialization.jsonobject(with: data) let prettydata = try jsonserialization.data(withjsonobject: dataasjson, options: .prettyprinted) return prettydata } catch { return data // fallback to original data if it can't be serialized. } }
let provider = moyaprovider<myapiservice>(plugins: [networkloggerplugin(verbose: true, responsedataformatter: jsonresponsedataformatter)])