There are very powerful Retrofit requests in Android development, and RxJava makes it very easy to implement RESTful API web requests. There is also the very powerful network request library Moya in iOS development, Moya is a lightweight Swift network layer developed based on Alamofire. Moya is very scalable and can be easily combined with RXSwift and ObjectMapper.

Test the REST API definition

Let’s start by defining a few REST apis on the server side that developers can implement on their own terms.

Request error format instance
{
    "error": "Password error"."error_code": "password_error"
}
Copy the code
List of test apis
  1. http://127.0.0.1:8080/account/login, parameters of the username, password, a post request, successful response to the User.
  2. http://127.0.0.1:8080/user/ {username}, get request, successful response to the User.
  3. http://127.0.0.1:8080/user/query?q= {keyword}, get request, successful response to the User list.

Create the interface

// MyApiService.swift
import Moya

enum MyApiService {
    case login(username:String,password:String)
    case user(userId:String)
    case userQuery(keyword:String)}extension MyApiService:TargetType{
    // Define the requested host
    var baseURL: URL {
        return URL(string: "http://127.0.0.1:8080")!
    }
    // Define the path of the request
    var path: String {
        switch self {
        case .login(_._) :return "/account/login"
        case .user(let userId):
            return "user/\(userId)"
        case .userQuery(_) :return "user/query"}}// Define the interface request mode
    var method: Moya.Method {
        switch self {
        case .login:
            return .post
        case .user,.userQuery:
            return .get}}// Define simulated data
    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)! }}// Build parameters
    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)}}// Build the request header
    var headers: [String : String]? {
        return ["Content-type": "application/json"]}}Copy the code

The request data

letProvider = MoyaProvider<MyApiService>() // Moya provides the most primitive request mode. The response data is binary provider.request(.user(userId:"101")){ result in
        // do something with the result
        lettext = String(bytes: result.value! .data, encoding: .utf8)print("text1 = \(text)"} // With RxSwift, the response data is binary 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):
            printProvider. 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)}}) // Convert data to json format using mapJSON and convert it to the most common 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
})
Copy the code
Request data: RxBlocking

RxBlocking uses the tutorial to request the network in a synchronous manner

import RxBlocking

do{
    let text = try provider.rx.request(.user(userId: "101")).mapJSON().toBlocking().first()
    print("text5 = \(text)")}catch{
    print(error)
}
Copy the code

In combination with ObjectMapper

The introduction of ObjectMapper
pod 'ObjectMapper'.'~ > 3.4'
Copy the code
Write RxSwift extension code
//  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 {
            returnMapper<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 = "Server is off. Please try again later."
            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 {
            returnMapper<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 = "Server is off. Please try again later."
            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
    }
}

Copy the code
Create a User class
// 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
}
Copy the code
test
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)
}
Copy the code

Print log

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.}}Copy the code
let provider = MoyaProvider<MyApiService>(plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)])
Copy the code