1. 程式人生 > IOS開發 >基於Moya、RxSwift和ObjectMapper實現REST API請求

基於Moya、RxSwift和ObjectMapper實現REST API請求

在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)])複製程式碼


原文地址