let provider = MoyaProvider<RequestApi>()
provider.rx.request(.ads(position: VALUE10))
.asObservable()
.mapString()
.mapObject(BaseListResponse<Ads>.self)
.subscribe { event in
switch event {
case .next(let data):
// 接口调用成功
print(data.data.data![0].title)
case .error(let error):
// 接口报错
print("error \(error)")
case .completed:
print("completed")
}
}.disposed(by: rx.disposeBag)
1、配置网络请求
import Foundation
import Moya
enum RequestApi {
// 广告列表
case ads(position:Int)
// 单曲列表
case songs
case songDetail(data:String)
// 登录注册
case register(data:User)
case login(data:User)
}
// MARK: - 实现TargetType协议
extension RequestApi:TargetType{
var baseURL: URL {
return URL(string: RequestConfig.BASE_URL)!
}
// 返回每个请求的路径
var path: String {
switch self{
case .ads(_):
return "v1/ads"
case .songs:
return "v1/songs"
case .songDetail(let data):
return "v1/songs/\(data)"
case .register(_):
return "v1/users"
case .login:
return "v1/sessions"
default:
fatalError("Request path is null")
}
}
var method: Moya.Method {
switch self{
case .login:
return .post
default:
return.get
}
}
var task: Moya.Task {
switch self{
case .ads(let position):
return .requestParameters(parameters: ["position":position], encoding: URLEncoding.default)
case .login(let user):
return .requestData((user.toJSONString()?.data(using: .utf8))!)
case .register(let data):
return .requestData(data.toJSONString()!.data(using: .utf8)!)
default:
//不传递任何参数
return .requestPlain
}
}
/// 请求头
var headers: [String : String]? {
var headers:Dictionary<String,String> = [:]
//内容的类型
headers["Content-Type"]="application/json"
//判断是否登录了
if PreferenceUtil.isLogin() {
//获取token
let session = PreferenceUtil.getSession()
print("user session \(session)")
//将token设置到请求头
headers["Authorization"]=session
}
return headers
}
}
2、定义网络请求方法
import Foundation
import Moya
import RxSwift
import HandyJSON
class DoRequest{
static let shared = DoRequest()
private var provider:MoyaProvider<RequestApi>!
// MARK: 获取列表数据
func getAdsList() ->Observable<BaseListResponse<Ads>> {
return provider.rx
.request(.ads(position: 0))
.asObservable()
.mapString()
.mapObject(BaseListResponse<Ads>.self)
}
// MARK: 获取详情
func getSongDetail(_ data: String) -> Observable<BaseDetailResponse<Song>> {
return provider.rx
.request(.songDetail(data: data))
.asObservable()
.mapString()
.mapObject(BaseDetailResponse<Song>.self)
}
// MARK: 登录
func doLogin(_ data: User) -> Observable<BaseDetailResponse<BaseModel>> {
return provider.rx
.request(.login(data: data))
// 过滤出'statusCode'在200 - 299范围内的响应 成功响应的
.filterSuccessfulStatusCodes()
.asObservable()
.mapString()
.mapObject(BaseDetailResponse<BaseModel>.self)
}
/// 私有构造方法 主要为了打印后台请求到的数据 便于查看
private init() {
//插件列表
var plugins:[PluginType] = []
if Config.DEBUG {
//表示当前是调试模式
//添加网络请求日志插件
plugins.append(NetworkLoggerPlugin(configuration: NetworkLoggerPlugin.Configuration(logOptions: .verbose)))
}
//网络请求加载对话框
let networkActivityPlugin = NetworkActivityPlugin { change, target in
//changeType类型是NetworkActivityChangeType
//通过它能监听到开始请求和结束请求 //targetType类型是TargetType
//就是我们这里的service //通过它能判断是那个请求
if change == .began {
//开始请求
let targetType = target as! RequestApi
switch targetType {
case .sheetDetail:
DispatchQueue.main.async {
//切换到主线程 才可以操作view
ToastPopup.showLoading()
}
default:
break
}
} else {
//结束请求
DispatchQueue.main.async {
ToastPopup.hideLoading()
}
}
}
plugins.append(networkActivityPlugin)
provider = MoyaProvider<RequestApi>(plugins: plugins)
}
}
3、使用
DoRequest.shared
.getAdsList()
.subscribeSuccess { [weak self] data in
// [weak self]弱引用 防止内存泄露
print(data)
}.disposed(by: rx.disposeBag)
import Foundation
import HandyJSON
// 通用模型
class BaseModel: HandyJSON {
required init() {}
func mapping(mapper: HelpingMapper) {
}
}
// 通用网络请求模型
class BaseResponse: BaseModel{
// 状态码
var status:Int = 0
// 发生网络请求错误的时候
var message:String?
}
import UIKit
import HandyJSON
class BaseCommon: BaseModel {
/// Id
var id:String!
/// 创建时间
var createdAt:String!
/// 更新时间
var updatedAt:String!
override func mapping(mapper: HelpingMapper) {
super.mapping(mapper: mapper)
mapper <<< self.createdAt <-- "created_at"
mapper <<< self.updatedAt <-- "updated_at"
}
}
import Foundation
import HandyJSON
class Meta<T:HandyJSON>:BaseModel{
// 真实数据
var data:[T]?
// 条数
var total:Int!
// 页数
var pages:Int!
// 当前每页显示多少
var size:Int!
// 当前页
var page: Int!
// 下一页
var next:Int?
}
class BaseListResponse<T:HandyJSON>:BaseResponse{
// 分页元数据
var data:Meta<T>!
}
import Foundation
import HandyJSON
class BaseDetailResponse<T:HandyJSON>:BaseResponse{
/// 真实数据
/// 它的类型定义为范型
var data: T?
init(_ data: T) {
self.data = data
}
required init() {
super.init()
}
}
import Foundation
import HandyJSON
import Moya
import RxSwift
/// 自定义错误
///
/// - objectMapping: 表示JSON解析为对象失败
public enum JSONError: Swift.Error {
case objectMapping
}
extension Observable{
/// 将字符串解析为对象
///
/// - Parameter type: 要转为的类
/// - Returns: 转换后的观察者对象
public func mapObject<T:HandyJSON>(_ type: T.Type) -> Observable<T>{
map { data in
//将参数尝试转为字符串
guard let dataString = data as? String else{
//data不能转为字符串
throw JSONError.objectMapping
}
guard let result = type.deserialize(from: dataString) else{
throw JSONError.objectMapping
}
//解析成功
//返回解析后的对象
return result
}
}
}
// MARK: - 扩展ObservableType
// 目的是添加两个自定义监听方法
// 一个是只观察请求成功的方法
// 一个既可以观察请求成功也可以观察请求失败
extension ObservableType{
/// 观察成功和失败事件
///
/// - Parameter onSuccess: <#onSuccess description#>
/// - Returns: <#return value description#>
func subscribe(_ success:@escaping ((Element)-> Void),_ error: @escaping ((BaseResponse?,Error?)-> Bool)) -> Disposable {
//创建一个Disposable
let disposable = Disposables.create()
//创建一个HttpObserver
let observer = HttpObserver<Element>(success,error)
//创建并返回一个Disposables
return Disposables.create(self.asObservable().subscribe(observer),disposable)
}
/// 观察成功的事件
///
/// - Parameter onSuccess: <#onSuccess description#>
/// - Returns: <#return value description#>
func subscribeSuccess( _ success:@escaping ((Element)-> Void) ) -> Disposable {
let disposable = Disposables.create()
let observer = HttpObserver<Element>(success,nil)
return Disposables.create(self.asObservable().subscribe(observer),disposable)
}
}
//http网络请求观察者
public class HttpObserver<Element>:ObserverType{
/// ObserverType协议中用到了泛型E
/// 所以说子类中就要指定E这个泛型
/// 不然就会报错
public typealias E = Element
public typealias successCallback = ((E)-> Void)
/// 请求成功回调
var success:successCallback
/// 请求失败回调
var error:((BaseResponse?,Error?)-> Bool)?
var controller:BaseCommonController?
/// 构造方法
///
/// - Parameters:
/// - onSuccess: 请求成功的回调
/// - onError: 请求失败的回调
init(_ success:@escaping successCallback,_ error: ((BaseResponse?,Error?)-> Bool)?) {
self.success = success
self.error = error
}
/// 当RxSwift框架里面发送了事件回调
///
/// - Parameter event: <#event description#>
public func on(_ event: Event<Element>) {
switch event {
case .next(let data):
// print("HttpObserver next \(data)")
//将值尝试转为BaseResponse
let baseResponse = data as? BaseResponse
if baseResponse?.status != 0 {
//状态码不等于0
//表示请求出错了
handlerResponse(baseResponse:baseResponse)
} else {
//请求正常
success(data)
}
case .error(let error):
//请求失败
// print("HttpObserver error:\(error)")
handlerResponse(error:error)
case .completed:
//请求完成
// print("HttpObserver completed")
break
}
}
/// 尝试处理错误网络请求
///
/// - Parameters:
/// - baseResponse: 请求返回的对象
/// - error: 错误信息
func handlerResponse(baseResponse:BaseResponse?=nil,error:Error?=nil) {
if self.error != nil && self.error!(baseResponse,error) {
//回调失败block
//返回true,父类不自动处理错误
//子类需要关闭loading,当前也可以父类关闭
//暴露给子类的原因是,有些场景会用到
//例如:请求失败后,在调用一个接口,如果中途关闭了
//用户能看到多次显示loading,体验没那么好
} else {
//自动处理错误
ExceptionHandleUtil.handlerResponse(baseResponse,error)
}
}
}
import Foundation
import Moya
import Alamofire
class ExceptionHandleUtil{
/// 处理网络响应
/// - Parameters:
/// - data: <#data description#>
/// - error: <#error description#>
static func handlerResponse(_ data:BaseResponse?=nil,_ error:Error?=nil) {
if error != nil {
//先处理有异常的请求
handlerError(error!)
} else {
if let r = data?.message{
//有错误提示
ToastPopup.shortShow(r)
} else {
ToastPopup.shortShow(R.string.localizable.errorUnknown())
}
}
}
/// 处理错误
/// - Parameter error: <#error description#>
static func handlerError(_ error:Error) {
if let error = error as? MoyaError {
switch error {
case .stringMapping(_):
ToastPopup.shortShow("响应转为字符串错误")
case .statusCode(let response):
//响应码
let code=response.statusCode
handleHttpError(code)
case .underlying(let _ as NSError, _):
//这里直接判断nsError.code有问题
//目前也没找到好的解决方法,暂时用这个方法解决:https://github.com/Moya/Moya/issues/2059
//NSError错误code对照表:https://www.jianshu.com/p/9c9f14d25572
if let alamofireError = error.errorUserInfo["NSUnderlyingError"] as? Alamofire.AFError,
let underlyingError = alamofireError.underlyingError as? NSError{
switch underlyingError.code {
case NSURLErrorNotConnectedToInternet:
//没有网络连接,例如:关闭了网络
ToastPopup.shortShow(R.string.localizable.networkError())
case NSURLErrorTimedOut:
//连接超时,例如:网络特别慢
ToastPopup.shortShow(R.string.localizable.errorNetworkTimeout())
case NSURLErrorCannotFindHost:
//域名无法解析,例如:域名写错了
ToastPopup.shortShow(R.string.localizable.errorNetworkUnknownHost())
case NSURLErrorCannotConnectToHost:
//无法连接到主机,例如:解析的ip地址,或者直接写的ip地址无法连接
ToastPopup.shortShow(R.string.localizable.errorNetworkUnknownHost())
default:
ToastPopup.shortShow(R.string.localizable.errorUnknown())
}
}else{
ToastPopup.shortShow(R.string.localizable.errorUnknown())
}
default:
ToastPopup.shortShow(R.string.localizable.errorUnknown())
}
}
}
static func handleHttpError(_ data:Int) {
switch data {
case 401:
ToastPopup.shortShow(R.string.localizable.errorNetworkNotAuth())
// AppDelegate.shared.logout()
case 403:
ToastPopup.shortShow(R.string.localizable.errorNetworkNotPermission())
case 404:
ToastPopup.shortShow(R.string.localizable.errorNetworkNotFound())
case 500..<599:
ToastPopup.shortShow(R.string.localizable.errorNetworkServer())
default:
ToastPopup.shortShow(R.string.localizable.errorUnknown())
}
}
}