基于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)])
相关文章
|
6天前
|
开发框架 监控 .NET
Visual Basic的Web服务和REST API开发指南
【4月更文挑战第27天】本文探讨了使用Visual Basic(VB.NET)构建Web服务和RESTful API的方法。首先介绍了Web服务的基础和REST API的概念,然后阐述了.NET Framework与.NET Core/.NET 5+对VB.NET的支持,以及ASP.NET Core在Web开发中的作用。接着,详细讲解了创建RESTful API的步骤,包括控制器与路由设置、模型绑定与验证,以及返回响应。此外,还讨论了安全措施、测试方法、部署选项和监控策略。最后强调,VB.NET开发者可以通过ASP.NET Core涉足现代Web服务开发,拓宽技术领域。
|
14天前
|
安全 Java API
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(上)
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)
34 0
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(上)
|
3天前
|
分布式计算 DataWorks 关系型数据库
DataWorks操作报错合集之在DataWorks同步数据时,遇到乱码问题,该怎么解决(rest api数据源)
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
12 0
|
5天前
|
JSON 安全 API
【专栏】浅谈 REST API 身份验证的四种方法
【4月更文挑战第28天】本文探讨了四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥。基本认证简单但不安全;OAuth适用于授权第三方应用;JWT提供安全的身份验证信息传递;API密钥适合内部使用。选择方法时需平衡安全性、用户体验和开发复杂性。
|
10天前
|
JSON 测试技术 API
Python的Api自动化测试使用HTTP客户端库发送请求
【4月更文挑战第18天】在Python中进行HTTP请求和API自动化测试有多个库可选:1) `requests`是最流行的选择,支持多种请求方法和内置JSON解析;2) `http.client`是标准库的一部分,适合需要低级别控制的用户;3) `urllib`提供URL操作,适用于复杂请求;4) `httpx`拥有类似`requests`的API,提供现代特性和异步支持。根据具体需求选择,如多数情况`requests`已足够。
13 3
|
11天前
|
JSON JavaScript API
访问REST API:在Vue中消费和管理远程数据
【4月更文挑战第23天】本文探讨了在Vue应用中高效访问REST API的方法,包括选择合适的API、使用Axios或Fetch发送请求、封装API服务、处理响应和数据、错误管理及性能优化。关键点在于创建服务层封装请求,使用计算属性和方法处理数据,以及实施错误处理和性能提升策略。通过这些最佳实践,开发者能更好地管理和消费远程数据,构建出动态、响应式的Vue应用。
|
14天前
|
安全 Java API
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(下)
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)
22 0
|
14天前
|
安全 Java API
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(中)
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)
29 0
|
23天前
|
API 网络架构 开发者
邮箱REST API发送邮件有什么方法步骤?
AokSend指南:通过邮箱REST API发送邮件涉及6步。首先,研究邮箱服务提供商的API文档;其次,获取API密钥和身份验证信息;然后,用编程语言构建包含收件人、发件人、主题、正文和附件的API请求;接着,使用HTTP POST发送请求到API端点;收到响应后处理邮件发送状态;最后,进行错误处理。AokSend提供高效稳定的触发式及SMTP/API接口,支持大量验证码发送。在使用过程中,遵守服务条款和隐私政策。
|
23天前
|
API 网络架构 开发者
邮箱REST API发送邮件有什么方法步骤?
通过邮箱REST API发送邮件涉及理解API文档、获取API密钥,构建包含收件人、发件人、主题等信息的请求,然后发送并处理响应。记得做错误处理,并遵循服务提供商的使用条款。AokSend提供高效稳定的邮件发送服务,支持SMTP/API接口。