当前位置: 首页 > 工具软件 > Moya > 使用案例 >

iOS开发之Moya网络请求使用与封装

高墨一
2023-12-01

一、使用

        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()
    }
}
  • mapObject()封装,字符串转对象

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
        }
    }
}
  • subscribeSuccess() 成功转换
// 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)
    }
}
  • HttpObserver() 网络请求观察者
//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())
        }
    }
}

 类似资料: