在Android開發中有非常強大的 Retrofit 請求,結合RxJava可以非常方便實現 RESTful API 網路請求。在 iOS開發中也有非常強大的網路請求庫 "Moya" ,Moya是一個基於 Alamofire 開發的,輕量級的Swift網路層。Moya的可擴展性非常強,可以方便和RX ...
在Android開發中有非常強大的 Retrofit 請求,結合RxJava可以非常方便實現 RESTful API 網路請求。在 iOS開發中也有非常強大的網路請求庫 Moya ,Moya是一個基於 Alamofire 開發的,輕量級的Swift網路層。Moya的可擴展性非常強,可以方便和RXSwift、ObjectMapper結合。
測試 REST API 定義
我們先用服務端定義幾個REST API,開發者根據自己的條件來實現。
請求錯誤格式實例
{
"error": "密碼錯誤",
"error_code": "password_error"
}
測試 API 列表
- http://127.0.0.1:8080/account/login,參數username、password,post請求,成功響應為User。
- http://127.0.0.1:8080/user/{userId},get請求,成功響應為User。
- 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)])