Swift笔记39:Moya

Moya 基于 Alamofire Alamofire基础上封装的网络请求库。Alamofire对Request、Response、Session等进行了抽象。Moya不仅对Request(TargetType)、Response进行抽象,而且多了MoyaProvider、Endpoint、Plugin、Stub的概念。让我们可以更好的同一关联API以及相关配置,支持插件管理,单元测试等。

查看demo

下面图片显示了封装的层级。

2024-11-28 .png
2024-12-02 21.00.png
Moya的具体实现

一、 我们先创建一个枚举,同一管理我们的项目中可能用到的接口

enum API {
    case login(params: [String: Any])
    // 获取分页数据
    case getBookList(_ page: Int)
    ///  上传图片
    case uploadImage(image: Data)
    /// 其他接口...
    case other
}

二、 API枚举遵守TargetType协议,并实现协议方法
var baseURL: URL {}
var path: String {}
var method: Moya.Method {}
var task: Moya.Task {}

extension API: TargetType {
    // 服务器地址,基础域名
    var baseURL: URL {
        switch self {
        case .login:
            return URL(string: kBaseURL)!
        case .getBookList:
            return URL(string: kBaseURL)!
        default:
            break
        }
    }
    
    // 每个接口的相对路径(请求时的绝对路径是   baseURL + path)
    var path: String {
        switch self {
        case .login:
            return "/mock/login"
        case .getBookList:
            return "app/mock/303994/test/dbbooklist"
        case .uploadImage:
            return "/mock/uploadImage"
        case .other:
            return ""
        }
    }
    
    // 接口请求方式
    var method: Moya.Method {
        switch self {
        case
            .getPageList,
            .other
            return .get
        case
            .login:
            .uploadImage,
            return .post
        }
    }
    
    // 3.请求任务事件(这里附带上参数).
    var task: Task {
        var params: [String: Any] = [:]
        switch self {
        case .login:
            return .requestPlain
        case let .getPageList(page):
            params["page"] = page
            params["limit"] = 15
            params["maxCount"] = 100
        case .uploadImage(let image):
            return .uploadMultipart([MultipartFormData(provider: .data(image), name: "image")])
        default:
            break
        }
        return .requestParameters(parameters: params, encoding: URLEncoding.default)
    }    
}

三、设置请求头,单元测试

public extension TargetType {
    //设置请求头
    var headers: [String: String]? {
        return nil
    }
    //这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
    var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
}

四、公共参数

extension URLRequest {
    // TODO:处理公共参数
    private var commonParams: [String: Any]? {
        // 所有接口的公共参数添加在这里:
        let header = [
            "Content-Type": "application/x-www-form-urlencoded",
            "systemType": "iOS",
            "version": "1.0.0",
            "token": getToken(),
        ]
        return header
        // 如果不需要传空
        //        return nil
    }

五、创建插件,插件可在创建MoyaProvider中自由选择,下面的第一个是负责监听网络加载进度,第二个负责打印,第三个是定义URLRequest参数编码

/// NetworkActivityPlugin插件用来监听网络请求,界面上做相应的展示
let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
    print("networkPlugin \(changeType)")
    switch changeType {
    case .began:
        mLog("开始请求网络")
        SVProgressHUD .setDefaultMaskType(SVProgressHUDMaskType.clear)
        SVProgressHUD .setBackgroundLayerColor(UIColor .blue)
        SVProgressHUD .setDefaultStyle(SVProgressHUDStyle.light)
        SVProgressHUD .setForegroundColor(.green)
        SVProgressHUD .setDefaultAnimationType(SVProgressHUDAnimationType.flat)
        SVProgressHUD .show(withStatus: "加载中")
        SVProgressHUD .setMinimumDismissTimeInterval(20.0)
    case .ended:
        mLog("结束")
        SVProgressHUD .dismiss()
    }
}

class LoggingPlugin: PluginType {
    func willSend(_ request: RequestType, target: TargetType) {
        guard let requstTemp = request.request else { return }
        #if DEBUG
        print("********** MoyaRequest-start ***********\n",
              "完整路径:\n",
              "\(requstTemp.description)\n",
              "方法:\n",
              "\(requstTemp.httpMethod ?? "")\n",
              "Task参数:\n\(target.task)\n",
              "baseURL:\n\(target.baseURL)\n",
              "********** MoyaRequest-end ***********\n")
        #endif
    }
}

class RequestHandlingPlugin: PluginType {
    public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        var mutateableRequest = request
        return mutateableRequest.appendCommonParams()
    }
}

extension URLRequest {
    mutating func appendCommonParams() -> URLRequest {
        let request = try? encoded(parameters: commonParams, parameterEncoding: URLEncoding(destination: .queryString))
        assert(request != nil, "append common params failed, please check common params value")
        return request!
    }

    func encoded(parameters: [String: Any]?, parameterEncoding: ParameterEncoding) throws -> URLRequest {
        do {
            return try parameterEncoding.encode(self, with: parameters)
        } catch {
            throw MoyaError.parameterEncoding(error)
        }
    }
}

六、网络请求处理

public class MoyaHttpCenter {
    public class func request<T: TargetType>(_ target: T, success: @escaping ((Any) -> Void), failure: ((Int?, String) -> Void)?) {
        let provider = MoyaProvider<T>(plugins: [
            RequestHandlingPlugin(), networkPlugin, LoggingPlugin()
        ])
        
        provider.request(target) { result in
            switch result {
            case let .success(response):
                do {
                    let resObject = try? response.mapJSON()
                    let responseObject = JSON(resObject ?? "")
                    let code = responseObject["code"].intValue
                    let msg = String(describing: responseObject["msg"])
                    switch code {
                    case 0:
                        // 数据返回正确
                        success(responseObject)
                    case 1001:
                        success(responseObject)
                    case 1002:
                        success(responseObject)
                    default:
                        failureHandle(failure: failure, stateCode: code, message: msg)
                    }
                }
            case let .failure(error):
                mLog(message)
            }
        }
        
        func failureHandle(failure: ((Int?, String) -> Void)?, stateCode: Int?, message: String) {
            showLoading(message)
            failure?(stateCode, message)
        }
    }
}
func getNetModel() {
    MoyaHttpCenter.request(API.getPageList(1)) { [weak self] json in
        mAllLog(JSON(json))
    } failure: { code, msg in
        mAllLog("code : \(code!)")
        mLog("message : \(msg)")
    }
}

上传图片

    func uploadImage<T:TargetType>(_ target: T, _ image: Image, success: @escaping ((Any) -> Void), failure: ((Int?, String) -> Void)?) {
//        let image = UIImage(named: "your-image-name")
//        uploadImage(image: image)
        let provider = MoyaProvider<T>()
        provider.request(target) { result in
            switch result {
            case let .success(response):
                do {
                    let resObject = try? response.mapJSON()
                    let responseObject = JSON(resObject ?? "")
                    let code = responseObject["code"].intValue
                    let msg = String(describing: responseObject["msg"])
                    switch code {
                        // 测试接口中没有返回code
                        // 这里直接让他返回成功数据
                    case 0:
                        success(responseObject)
                    default:
                        // 其他错误
                        failure?(code, "图片上传失败")
                    }
                }
            case let .failure(error):
                let statusCode = error.response?.statusCode ?? 1000
                failure?(statusCode, "图片上传失败")
            }
        }
    }
}

以上代码我们就封装了一个统一管理的接口
核心是围绕provider的创建

 let provider = MoyaProvider<T>(plugins: [
        RequestHandlingPlugin(), activityPlugin, LoggingPlugin()
 ])

我们可以注意到provider的init源码,上面我们都是实现了协议,然后使用默认的两个参数
EndpointClosure = MoyaProvider.defaultEndpointMapping
EndpointClosure = (Target) -> Endpoint //类型

RequestClosure = MoyaProvider.defaultRequestMapping
public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void

/// Initializes a provider.
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
            requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
            stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
            callbackQueue: DispatchQueue? = nil,
            manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
            plugins: [PluginType] = [],
            trackInflights: Bool = false) {

    self.endpointClosure = endpointClosure
    self.requestClosure = requestClosure
    self.stubClosure = stubClosure
    self.manager = manager
    self.plugins = plugins
    self.trackInflights = trackInflights
    self.callbackQueue = callbackQueue
}

我们可以对这两个参数自定义,然后自定义打印或设置Request超时

private let myEndpointClosure = { (target: API) -> Endpoint in
    let url = target.baseURL.absoluteString + target.path
    var task = target.task
    var endpoint = Endpoint(
        url: url,
        sampleResponseClosure: { .networkResponse(200, target.sampleData) },
        method: target.method,
        task: task,
        httpHeaderFields: target.headers
    )
    requestTimeOut = 30
    switch target {
    case .getPageList(_ page: Int):
        requestTimeOut = 5
        return endpoint
    default:
        return endpoint
    }
}
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        // 设置请求时长
        request.timeoutInterval = requestTimeOut
        // 打印请求参数
        if let requestData = request.httpBody {
#if DEBUG
            let parmeter = String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? ""
            print("********** MoyaRequest-start ***********\n",
                  "完整路径:\n",
                  "\(request.url!)\n",
                  "方法:\n",
                  "\(request.httpMethod ?? "")\n",
                  "参数:\(parmeter)\n",
                  "********** MoyaRequest-end ***********\n")
#endif
            parmeterStr = String(data: request.httpBody!, encoding: String.Encoding.utf8)!
            // [URL absoluteString];
        } else {
            print("\(request.url!)" + "\(String(describing: request.httpMethod))")
        }
        done(.success(request))
    } catch {
        done(.failure(error)
    }
}

最后我们重新创建,并使用Provider.request

let provider = MoyaProvider<API>(endpointClosure: myEndpointClosure, requestClosure: requestClosure, plugins: [], trackInflights: false)
NetWorkRequest(API.getPageList(1), completion: {data in
    mAllLog(JSON(data))
}) { errorString in
    mLog(errorString)
}

参考资料
GitHub地址: https://github.com/Moya/Moya
https://github.com/LeoMobileDeveloper/Blogs/blob/master/Swift/AnaylizeMoya.md
https://www.jianshu.com/p/9643896821eb
https://www.jianshu.com/p/9e80c29c1856

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容