There are very powerful Retrofit requests in Android development, and it is very convenient to implement RESTful API network requests with RxJava. There are also very powerful network request libraries in iOS development Moya Moya is a lightweight Swift network layer based on Alamoire. Moya's scalability is very strong, and it can be easily combined with RXSwift and ObjectMapper.
Testing REST API definitions
We first define several REST API s with the server side, and the developers implement them according to their own conditions.
Examples of Request Error Format
{ "error": "Password error", "error_code": "password_error" }
List of test API s
- http://127.0.0.1:8080/account/login, parameter username, password, post request, successful response to User.
- http://127.0.0.1:8080/user/{userId}, get request, successful response to User.
- http://127.0.0.1:8080/user/query?q={keyword}, get request, successful response to User list.
Create 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 host of the request 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)! } } // Constructing 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 request header var headers: [String : String]? { return ["Content-type": "application/json"] } }
Request data
let provider = MoyaProvider<MyApiService>() // Moya provides the most primitive way of requesting, and the data it responds to is binary. provider.request(.user(userId: "101")){ result in // do something with the result let text = String(bytes: result.value!.data, encoding: .utf8) print("text1 = \(text)") } // Combined 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): print(error) } }) // Converting data into json format through mapJSON 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) } }) // Converting data into json format through mapJSON and into 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 })
Request data: RxBlocking
RxBlocking Use Tutorial The network can be requested synchronously
import RxBlocking do{ let text = try provider.rx.request(.user(userId: "101")).mapJSON().toBlocking().first() print("text5 = \(text)") }catch{ print(error) }
Combining ObjectMapper
Introducing ObjectMapper
pod 'ObjectMapper', '~> 3.4'
Writing 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 { 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 = "Server desertion, 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 { 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 = "Server desertion, 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 } }
Create User Classes
// 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 }
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) }
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. } }
let provider = MoyaProvider<MyApiService>(plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)])