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

swift 使用Moya进行网络请求

宫俊才
2023-12-01

前言

测试阶段,还未曾放到项目中使用,后续会继续优化调整,初始版本

环境

  pod 'Moya',  '14.0.0'
  pod 'HandyJSON',    '5.0.3-beta'
  Xcode 13.2

用法

1、基本模板

Moya 在对于 API 的封装是基于 enum,通过对于枚举不同端点的不同用法,生成请求。
如果项目小可以只有一个API.swift, 如果项目比较大,可以分模块,分成几个API.swift

public enum HJApi {
    case zen
    case version([String: String])  
    case sendMsg(String)
    case uploadHeadImage(parameters: [String:Any], imageDate:Data)
}

extension HJApi: TargetType {
///域名
    public var baseURL: URL {
        switch self {
        case .version(_):
            return URL(string: "www.abc.com")!
        default:
            return URL(string: "www.def.com")!
        }
    }
    ///请求地址放到这里
    public var path: String {
        switch self {
        case .zen:
            return "/abc/abcdef"
        case .version(_):
            return "/abc/abcdefghi"
        case .sendMsg(let msg):
            return "/abcd/abcdef/\(msg)" 
        case .uploadHeadImage(parameters: _, imageDate: _):
            return "/file/user/upload.jhtml"
        }
    }
    ///接口的请求类型
    public var method: Moya.Method {
        switch self {
        case .zen:
            return .get
        default:
            return .post
        }
    }
	///请求的参数在这里处理
    public var task: Task {
        switch self {
        case .version(let parameters): 
            return .requestParameters(parameters: parameters, encoding: JSONEncoding.default)
        case .uploadHeadImage(parameters: let parameters, imageDate: let imgDate):
            let formData = MultipartFormData(provider: .data(imgDate), name: "file",
                                              fileName: "hangge.png", mimeType: "image/png")
            return .uploadCompositeMultipart([formData], urlParameters: parameters)
        default:
            return .requestPlain
        }
    }
    
    public var validationType: ValidationType {
        return .successCodes
    }
    // 用于单元测试
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
    public var headers: [String: String]? {
        return [
            "content-type": "application/json;charset=utf-8;",
            "platform": "ios",
        ]
    }
}

2.网络请求初始化

/// 网络请求发送的核心初始化方法,创建网络请求对象
private let provider = MoyaProvider<MultiTarget>(endpointClosure: endpointClosure, requestClosure: requestClosure, plugins: [networkPlugin], trackInflights: false)
///超时时长
private var requestTimeOut: Double = 30
/// 网络请求的基本设置,这里可以拿到是具体的哪个网络请求,可以在这里做一些设置
private let endpointClosure = {(target: TargetType) -> 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)
    if let apiTarget = target as? MultiTarget,
       let tar = apiTarget.target as? HJApi  {
        switch tar {
        case .zen:
            requestTimeOut = 20
            return endPoint
        default:
            return endPoint
        }
    }
    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 {
            print("请求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
        } else {
            print("请求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
        }

        if let header = request.allHTTPHeaderFields {
            print("请求头内容\(header)")
        }

        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error, nil)))
    }
}
/// NetworkActivityPlugin插件用来监听网络请求,界面上做相应的展示
/// 但这里我没怎么用这个。。。 loading的逻辑直接放在网络处理里面了
private let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
    print("networkPlugin \(changeType)")
    // targetType 是当前请求的基本信息
    switch changeType {
    case .began:
        print("开始请求网络")

    case .ended:
        print("结束")
    }
}
///返回数据
struct myResponseData: HandyJSON {
    var isSuccess: Bool?
    var code: String?
    var message: String?
    var data: String?
} 

3. 网络请求封装

此处是返回NetWork网络请求封装

public class NetWork: NSObject {  
}

在NETWork 里面的返回单个model和数组model以及字符串的网络请求封装,

///返回单 model 网络请求
    public class func request<T:HandyJSON>(target: TargetType,
                                           modelType: T.Type,
                                           successBlock: @escaping (_ code: String, _ model: T?,  _ msg:String) -> Void,
                                           failureBlock:@escaping (_ code: String, _ msg:String) -> Void){
        
        provider.request(MultiTarget(target)) { result in
            switch result {
            case let .success(response):
                if let json = try? response.mapJSON() {
                    responseData(json, { code, result, msg  in
                        return successBlock(code, modelType.deserialize(from: result), msg)
                    }, failureBlock)
                } else {
                    return failureBlock("-1", "返回数据获取失败")
                }
            case let .failure(error):
                print("failure\(error.localizedDescription)")
                return failureBlock("-1", msgNetError)
            }
        }
    }
   

返回数组 model 网络请求

 ///返回数组 model 网络请求
    public class func request<T:HandyJSON>(target: TargetType,
                                           modelTypes: [T].Type,
                                           successBlock: @escaping (_ code: String, _ models: [T?], _ msg: String) -> Void,
                                           failureBlock:@escaping (_ code: String, _ msg:String) -> Void){
        
        provider.request(MultiTarget(target)) { result in
            switch result {
            case let .success(response):
                if let json = try? response.mapJSON() {
                    responseData(json, { code, result, msg in
                        return successBlock(code, modelTypes.deserialize(from: result) ?? [], msg)
                    }, failureBlock)
                } else {
                    return failureBlock("-1", "返回数据获取失败")
                }
            case let .failure(error):
                print("failure\(error.localizedDescription)")
                return failureBlock("-1", msgNetError)
            }
        }
    }

返回字符串 网络请求

    ///返回字符串 网络请求
    public class func request(target: TargetType,
                        successBlock: @escaping (_ code: String, _ result: String, _msg: String) -> Void,
                        failureBlock:@escaping (_ code: String, _ msg:String) -> Void){
        
        provider.request(MultiTarget(target)) { result in
            switch result {
            case let .success(response):
                if let json = try? response.mapJSON() {
                    responseData(json, { code, result, msg in
                        return successBlock(code, result, msg)
                    }, failureBlock)
                } else {
                    return failureBlock("-1", "返回数据获取失败")
                }
            case let .failure(error):
                print("failure\(error.localizedDescription)")
                return failureBlock("-1", msgNetError)
            }
        }
    }

数据解析封装

///数据处理
class func responseData(
    _ data: Any,
    _ successBlock: @escaping (_ code: String, _ result: String, _ msg:String) -> Void,
    _ failureBlock:@escaping (_ code: String, _ msg:String) -> Void) {
    
    if let obj = JSONDeserializer<myResponseData>.deserializeFrom(dict: data as? [String:Any]) {
        let message = obj.message ?? msgNetError
        guard let code = obj.code, !code.isEmpty else {
            return failureBlock("-1", message)
        }
        
        if "201" == code { 
            return failureBlock(code, message)
        }
        
        if "200" == code {
            guard let dataEncode:String = obj.data, !dataEncode.isEmpty else {
                return successBlock(code, "", message)
            } 
            DDLogInfo("\n<数据解析结果>:\n\(String(describing: result))")
            successBlock(code, result ?? "", message)
        }else{
            return failureBlock(code, message)
        }
    }else{
        return failureBlock("-1", msgNetError)
    }
}

3.网络请求:举个栗子吧

model

struct HJVersionUpdateModel: HandyJSON { 
    var versionUpgradeId: String?  
    var content: String? 
    var upgradeType: String? 
    var downLoadUrl: String? 
    var createTime: String?  
    var updateTime: String?  
} 

网络请求, 在这只需要传参就好了,请求方式,请求链接,已经在API.swift处理过了

let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let parameters = ["appType":"ios", "version": version]
NetWork.request(target: HJApi.version(parameters),
                  modelType: HJVersionUpdateModel.self) { code, model,msg in
                  
} failureBlock:nil}

遇到的问题:

1、post请求status: 400、405

在post的task 中,使用了.requestParameters(parameters: par, encoding: URLEncoding.default)
问题原因:encoding问题
如果是 POST请求为 JSONEncoding.default
如果是 GET请求为 URLEncoding.default 或者 task设置 .requestPlain
解决方法借鉴自:swift 框架 moya post 请求遇到的坑

public var task: Task {
    switch self {
        case .version(let parameters):
        return .requestParameters(parameters: parameters, encoding: JSONEncoding.default)  
    default:
    //get请求一下两种均可,建议第二种
        //return .requestParameters(parameters: [:], encoding: URLEncoding.default)
        return .requestPlain
    }
}

demo 传送门
demo链接https://gitee.com/hcapp/hjnet.git

 类似资料: