iOS日常开发

Moya封装实践 2023-05-13 周六

2023-05-14  本文已影响0人  勇往直前888

简介

从OC切换到Swift,还真的不习惯。网络层同事已经封装了Alamofire,能用,但是总感觉不爽。看到网上都推荐使用Moya,所以也试着封装了一下。实际尝试下来,确实比直接使用Alamofire方便很多,推荐使用Moya。

设计模式

感觉跟大话设计模式中提到的命令模式很像,书中用了一个烤肉店的例子来类比,其结构图如下:


image.png

命令(烤肉菜单)

import Foundation
import Moya

/// 枚举值,参数统一为字典,有可能为空
enum MoyaRequestCommand {
    /// resource模块
    case resourceNoticeDetail([String: Any])
    case resourceAdvertPage([String: Any])
}

extension MoyaRequestCommand: TargetType {
    var baseURL: URL {
        MoyaConfig.baseURL
    }

    var path: String {
        switch self {
        case .resourceNoticeDetail:
            return "/gateway/resource/notice/detail"
        case .resourceAdvertPage:
            return "/gateway/resource/advert/page"
        }
    }

    var method: Moya.Method {
        switch self {
        case .resourceNoticeDetail:
            return .get
        case .resourceAdvertPage:
            return .get
        }
    }

    var task: Moya.Task {
        /// 默认的公共参数,采用默认的编码
//        var defaultParams: [String: Any] = [:]

        switch self {
        case .resourceNoticeDetail(let params):
            /// Get请求需要这个,其实就是在url后面拼接参数
            return .requestParameters(parameters: params, encoding: URLEncoding.default)
        case .resourceAdvertPage(let params):
            /// Get请求需要这个,其实就是在url后面拼接参数
            return .requestParameters(parameters: params, encoding: URLEncoding.default)

//        default: break
        }

        /// 默认参数编码
//        return .requestParameters(parameters: defaultParams, encoding: JSONEncoding.default)
    }

    var headers: [String: String]? {
        MoyaConfig.headers
    }
}

执行者(烤肉串者)

结果处理

enum Result {
    case success(Data)
    case failure(Error)
}
/// 网络返回字段
struct MoyaNetworkDataModel: HandyJSON {
    var code: Int = 0
    var msg: String?
    var data: Any?
}

Log和Loading

    let provider = MoyaProvider<MoyaRequestCommand>(plugins: [NetworkLoggerPlugin(), NetworkActivityPlugin(networkActivityClosure: { change, target in
        
        /// 不需要loading的命令列在这里
        switch target {
        case MoyaRequestCommand.resourceAdvertPage:
            return;
        default: break
        }
        
        /// 添加loading
        switch change {
        case .began:
            OperationQueue.main.addOperation {
                HudUtil.show()
            }
        case .ended:
            OperationQueue.main.addOperation {
                HudUtil.hide()
            }
        }
    })])

数据回传方式

    /// async函数形式的接口
    func asyncRequest(command target: MoyaRequestCommand, isShowToast: Bool = true) async -> MoyaNetworkDataModel {
        /// 将回调改为async函数
        await withCheckedContinuation { continuation in
            doRequset(command: target, isShowToast: isShowToast) { model in
                continuation.resume(returning: model)
            }
        }
    }

封装形式

import Foundation
import Moya

class MoyaNetworkManager {
    /// 单例实例
    /// MoyaNetworkManager.sharedInstance.就是单例的用法
    static let sharedInstance = MoyaNetworkManager()
    
    /// async函数形式的接口
    func asyncRequest(command target: MoyaRequestCommand, isShowToast: Bool = true) async -> MoyaNetworkDataModel {
        /// 将回调改为async函数
        await withCheckedContinuation { continuation in
            doRequset(command: target, isShowToast: isShowToast) { model in
                continuation.resume(returning: model)
            }
        }
    }
    
    /// 网络执行者,禁止外部直接访问
    private let provider = MoyaProvider<MoyaRequestCommand>(plugins: [NetworkLoggerPlugin(), NetworkActivityPlugin(networkActivityClosure: { change, target in
        
        /// 不需要loading的命令列在这里
        switch target {
        case MoyaRequestCommand.resourceAdvertPage:
            return;
        default: break
        }
        
        /// 添加loading
        switch change {
        case .began:
            OperationQueue.main.addOperation {
                HudUtil.show()
            }
        case .ended:
            OperationQueue.main.addOperation {
                HudUtil.hide()
            }
        }
    })])
    
    /// 请求完成的回调
    typealias NetworkCompletion = (MoyaNetworkDataModel) -> Void
    
    /// 统一调用provider完成网络请求;统一处理错误:通常是toast以下
    /// 回调形式的数据返回,不推荐使用,这里设置为私有
    private func doRequset(command target: MoyaRequestCommand, isShowToast: Bool = true, completion: @escaping NetworkCompletion) {
        provider.request(target) { result in
            switch result {
            case .success(let response):
                do {
                    guard let json = try response.mapJSON() as? [String: Any] else {
                        let parseError = MoyaNetworkDataModel(code: -1, msg: "服务器返回的不是JSON数据")
                        if isShowToast {
                            ToastUtil.show(parseError.msg)
                        }
                        completion(parseError)
                        return
                    }
                    
                    guard let model = MoyaNetworkDataModel.deserialize(from: json) else {
                        let modelError = MoyaNetworkDataModel(code: -2, msg: "JSON数据转MoyaNetworkDataModel失败")
                        if isShowToast {
                            ToastUtil.show(modelError.msg)
                        }
                        completion(modelError)
                        return
                    }
                    
                    /// 判断逻辑问题;统一处理,通常是toast一下
                    if model.code != 200 {
                        if isShowToast {
                            ToastUtil.show(model.msg)
                        }
                    }
                    
                    /// 成功返回
                    completion(model)
                } catch {
                    let catchError = MoyaNetworkDataModel(code: -3, msg: "解析出错:\(error.localizedDescription)")
                    if isShowToast {
                        ToastUtil.show(catchError.msg)
                    }
                    completion(catchError)
                }
                
            case .failure(let error):
                let networkError = MoyaNetworkDataModel(code: -4, msg: "请求失败:\(String(describing: error.errorDescription))")
                if isShowToast {
                    ToastUtil.show(networkError.msg)
                }
                completion(networkError)
            }
        }
    }
}

调用者(服务员)

/// Resource模块接口封装
struct ResourceApi {
    /// 根据id获取广告详情页内容
    static func asyncNoticeDetail(id: String?) async -> Any? {
        guard let id = id else {
            return nil;
        }
        let model = await MoyaNetworkManager.sharedInstance.asyncRequest(command: .resourceNoticeDetail(["id": id]))
        return model.data
    }
    
    /// 根据id获取广告详情页内容
    static func asyncAdvertPage(_ page: String?) async -> Any? {
        guard let page = page else {
            return nil;
        }
        let model = await MoyaNetworkManager.sharedInstance.asyncRequest(command: .resourceAdvertPage(["page": page]))
        return model.data
    }
}

客户端(吃烤肉串的客人)

    func getBanners() {
//        let strURL = "https://test.pandabuy.com/gateway/resource/advert/page"
//        let parameter: [String: Any] = [
//            "page": "home",
//        ]
//
//        AFNetRequest().requestData(URLString: strURL, type: .get, parameters: parameter) { responseObject, error in
//            if error != nil {
//                print("Error: \(error?.description ?? "")")
//                return
//            }
//
//            if let _ = responseObject {
//                self.banners = responseObject?["data"] as! [[String: AnyObject]]
//                self.setUI()
//            }
//        }
        
        Task {
            if let data = await ResourceApi.asyncAdvertPage("home") as? [[String: AnyObject]] {
                self.banners = data
                self.setUI()
            }
        }
    }

文件夹结构

企业微信截图_32e04868-5231-4e41-be02-339311089d80.png

小结

参考文章

Swift之Moya使用和封装
Moya

上一篇 下一篇

猜你喜欢

热点阅读