基于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)])
相关文章
|
22天前
|
JSON API 数据格式
获取商品详情API的请求格式是什么
获取商品详情API的请求格式通常依赖于特定的电商平台或服务提供商,但一般遵循类似的结构。以下是一个概括性的说明,以及针对几个主流电商平台的示例:
|
1月前
|
缓存 API 网络架构
掌握现代API开发:GraphQL vs REST
【10月更文挑战第24天】本文深入探讨了现代API开发中两种主流技术——GraphQL和REST的设计理念、技术特点及实际开发中的对比分析。GraphQL通过声明式数据请求和强类型系统提供更高的灵活性和性能,而REST则以其无状态特性和成熟的生态系统见长。文章还讨论了两者在客户端-服务器交互、安全性和工具支持方面的优劣,帮助开发者根据项目需求做出明智选择。
|
2月前
|
缓存 监控 API
抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
抖音抖店API请求获取宝贝详情数据的调用频率受限,需遵循平台规则。开发者可通过提升账号等级、申请更高配额、优化业务逻辑(如缓存数据、异步处理、批量请求)及监控调整等方式来应对。
|
2月前
|
缓存 负载均衡 API
抖音抖店API请求获取宝贝详情数据、原价、销量、主图等参数可支持高并发调用接入演示
这是一个使用Python编写的示例代码,用于从抖音抖店API获取商品详情,包括原价、销量和主图等信息。示例展示了如何构建请求、处理响应及提取所需数据。针对高并发场景,建议采用缓存、限流、负载均衡、异步处理及代码优化等策略,以提升性能和稳定性。
|
1月前
|
JSON API 数据格式
携程API接口系列,酒店景点详情请求示例参考
携程API接口系列涵盖了酒店预订、机票预订、旅游度假产品预订、景点门票预订等多个领域,其中酒店和景点详情请求是较为常用的功能。以下提供酒店和景点详情请求的示例参考
|
2月前
|
JavaScript 前端开发 Java
多种语言请求API接口方法
每种语言和库的选择取决于具体需求、项目环境以及个人偏好。了解这些基本方法,开发者就可以根据项目需求选择合适的语言和库来高效地与API交互。
49 1
|
2月前
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
69 4
|
3月前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
2月前
|
Java API Maven
使用 Smart-doc 记录 Spring REST API
使用 Smart-doc 记录 Spring REST API
63 0
|
2月前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
40 0