欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

基于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 列表
  1. http://127.0.0.1:8080/account/login,参数username、password,post请求,成功响应为user
  2. http://127.0.0.1:8080/user/{userid},get请求,成功响应为user
  3. 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)])