基于Moya、RxSwift和ObjectMapper优雅实现REST API请求

简介: 在Android开发中有非常强大的 Retrofit 请求,结合RxJava可以非常方便实现 RESTful API 网络请求。在 iOS开发中也有非常强大的网络请求库 Moya ,Moya是一个基于 Alamofire 开发的,轻量级的Swift网络层。

在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)])
相关文章
|
8月前
|
JSON 监控 API
掌握使用 requests 库发送各种 HTTP 请求和处理 API 响应
本课程全面讲解了使用 Python 的 requests 库进行 API 请求与响应处理,内容涵盖环境搭建、GET 与 POST 请求、参数传递、错误处理、请求头设置及实战项目开发。通过实例教学,学员可掌握基础到高级技巧,并完成天气查询应用等实际项目,适合初学者快速上手网络编程与 API 调用。
795 130
|
10月前
|
JSON JavaScript 测试技术
用Postman玩转电商API:一键测试+自动化请求教程
Postman 是电商 API 测试的高效工具,涵盖基础配置、自动化测试、环境管理与请求自动化,助你快速提升开发效率。
|
XML JSON API
淘宝商品详情API的调用流程(python请求示例以及json数据示例返回参考)
JSON数据示例:需要提供一个结构化的示例,展示商品详情可能包含的字段,如商品标题、价格、库存、描述、图片链接、卖家信息等。考虑到稳定性,示例应基于淘宝开放平台的标准响应格式。
|
8月前
|
JSON 监控 测试技术
亚马逊:调用订单退款API自动化处理售后请求,缩短用户等待时间
在电商运营中,售后效率直接影响用户体验与平台声誉。亚马逊订单退款API为卖家提供自动化工具,通过编程方式高效处理退款请求,显著缩短用户等待时间。本文详解如何集成该API,实现退款流程自动化,提升响应速度与用户满意度。
314 0
|
9月前
|
人工智能 JSON JavaScript
【干货满满】API接口请求封装
在 Vue 项目中,常使用 Axios 与后台交互,它基于 Promise,支持浏览器和 Node.js,具备拦截请求、取消请求、JSON 转换等功能。本文介绍了 Axios 的安装、封装及使用方法,包括创建实例、请求拦截、响应处理、API 管理等内容,并提供了完整代码示例,便于统一管理和调用接口,适用于前后端分离开发模式。
|
JSON API 数据格式
Python 请求微店商品详情数据 API 接口
微店开放平台允许开发者通过API获取商品详情数据。使用Python请求微店商品详情API的主要步骤包括:1. 注册并申请API权限,获得app_key和app_secret;2. 确定API接口地址与请求参数,如商品ID;3. 生成签名确保请求安全合法;4. 使用requests库发送HTTP请求获取数据;5. 处理返回的JSON格式响应数据。开发时需严格遵循微店API文档要求。
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
949 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~