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
- http://127.0.0.1:8080/account/login, parameters of the username, password, a post request, successful response to the User.
- http://127.0.0.1:8080/user/ {username}, get request, successful response to the User.
- 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)
caseUserQuery (keyword:String)} extension MyApiService:TargetType{// Define the host var baseURL: URL {return URL(string: "http://127.0.0.1:8080")! } // Define the requested path 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:
returnVar 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)! 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"]}}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
The original address