网络知识 娱乐 Swift -《从0到1 - 5》:封装网络请求工具类(Alamofire + Moya + SwiftyJSON)和链式封装

Swift -《从0到1 - 5》:封装网络请求工具类(Alamofire + Moya + SwiftyJSON)和链式封装

在OC开发中网络请求通常都使用AFNetworking,在Swift虽然也可以使用,但是推荐使用Swift语法实现的网络请求库Alamofire
通常大家会对请求库进行一次或多次封装,方便维护,我也不另外。刚开始参考了很多文章,最终决定使用Alamofire + Moya + SwiftyJSON 实现网络请求工具类和API管理类
本篇末尾介绍另一种通过链式封装Alamofire的实现方式

GitHub Demo 地址

Alamofire是一个使用Swift开发的网络请求库,其开发团队是AFNetworking的原团队。它语法简洁,采用链式编程的思想,使用起来是相当的舒服。本质是基于NSURLSession进行封装。
Moya是对Alamofire的再次封装。
SwiftyJSON是数据解析

github地址:

Alamofire
Moya
SwiftyJSON

pods引用:

pod 'Alamofire', '4.9.1'
pod 'Moya', '13.0.1'
pod 'SwiftyJSON', '5.0.1'

首先需要创建3个Swift文件:
一个是网络请求工具类:JhHttpTool.swift
一个Moya配置文件:MoyaConfig.swift
一个API管理文件:APIManager.swift

接下来直接上代码

JhHttpTool

//
//  JhHttpTool.swift
//  JhSwiftDemo
//
//  Created by Jh on 2021/12/28.
//  网络请求工具类:Alamofire + Moya + SwiftyJSON
import Foundation
import Moya
import SwiftyJSON

public class JhHttpTool {
    /// 使用Moya的请求封装
    ///
    /// - Parameters:
    ///   - target: 请求API,TargetType里的枚举值
    ///   - success: 成功的回调
    ///   - error: 连接服务器成功但是数据获取失败
    ///   - failure: 连接服务器失败
    public class func request<T: TargetType>(_ target: T, success: @escaping((Any) -> Void), failure: ((Int?, String) ->Void)?) {
        let provider = MoyaProvider<T>(plugins: [
            RequestHandlingPlugin(),
            //            networkLoggerPlugin
        ])
        
        provider.request(target) { result in
            switch result {
            case let .success(response):
                //                let json = try? response.mapString()
                //                let responseObject = try? response.mapJSON()
                //                JhLog( responseObject ?? "" );
                do {
                    // *********** 这里可以统一处理错误码,弹出提示信息 ***********
                    let resObject = try? response.mapJSON()
                    let responseObject = JSON(resObject ?? "")
                    let code = responseObject["code"].intValue
                    let msg = String(describing: responseObject["msg"])
                    switch (code) {
                    case 200 :
                        // 数据返回正确
                        success(responseObject)
                    case 401:
                        // 请重新登录
                        failure!(code,msg)
                        alertLogin(msg)
                    default:
                        // 其他错误
                        failureHandle(failure: failure, stateCode: code, message: msg)
                    }
                }
            case let .failure(error):
                let statusCode = error.response?.statusCode ?? 1000
                let message = "请求出错,错误码:" + String(statusCode)
                JhAllLog(message)
                failureHandle(failure: failure, stateCode: statusCode, message: error.errorDescription ?? message)
            }
        }
        
        // 错误处理 - 弹出错误信息
        func failureHandle(failure: ((Int?, String) ->Void)? , stateCode: Int?, message: String) {
            Alert.show(type: .error, text: message)
            failure?(stateCode ,message)
        }
        
        // 登录弹窗 - 弹出是否需要登录的窗口
        func alertLogin(_ title: String?) {
            // TODO: 跳转到登录页的操作:
        }
        
    }
    
    // MARK: - 打印日志
    //    static let networkLoggerPlugin = NetworkLoggerPlugin(verbose: true, cURL: true, requestDataFormatter: { data -> String in
    //        return String(data: data, encoding: .utf8) ?? ""
    //    }) { data -> (Data) in
    //        do {
    //            let dataAsJSON = try JSONSerialization.jsonObject(with: data)
    //            let prettyData =  try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
    //            return prettyData
    //        } catch {
    //            return data
    //        }
    //    }
}

MoyaConfig

//
//  JhHttpRequest.swift
//  JhSwiftDemo
//
//  Created by Jh on 2022/2/10.
//  Moya 配置文件
import Foundation
import Moya


// MARK: - 1、2需要根据项目进行更改
/**
 1、配置TargetType协议可以一次性处理的参数
 - Todo: 根据自己的需要更改,不能统一处理的移除下面的代码,并在APIManager中实现
 **/
public extension TargetType {
//    // 放到APIManager中了
//    var baseURL: URL {
//        return URL(string: "http://xxxxx")!
//    }
    var headers: [String : String]? {
        return nil
    }

    var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
}

/**
 2、公共参数
 - Todo: 配置公共参数,例如所有接口都需要传token,version,time等,就可以在这里统一处理
 - Note: 接口传参时可以覆盖公共参数。下面的代码只需要更改 【private var commonParams: [String: Any]?】
 **/
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
    }
    
    private func getToken() -> String {
        return "1"
    }
}


//下面的代码不更改
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)
        }
    }
}

APIManager

//
//  APIManager.swift
//  JhSwiftDemo
//
//  Created by Jh on 2021/12/28.
//  接口管理
import Foundation
import Moya

/// 基础域名
let kBaseURL = "https://www.fastmock.site/mock/1010b262a743f0b06c565c7a31ee9739/root"

enum  API {

    case login(params:Dictionary<String,Any>)
    // 获取分页数据
    case getPageList(_ page:Int)
    // 获取分组分页数据
    case getGroupPageList(page:Int)
    // 获取联系人数据
    case getContact
    // 获取微信运行排行榜
    case getWxMotionTops
    // 获取固定数据
    case getSimpleArrDic
    ///其他接口...
    case other1(p1: String, p2: Int, p3: String, p4: String)
    case other2

}

// MARK: - 补全【MoyaConfig 3:配置TargetType协议可以一次性处理的参数】中没有处理的参数
extension API: TargetType {
    
    //0. 基础域名,整个项目只用一个,可以写在MoyaConfig中
    var baseURL: URL {
        switch self {
        case .login:
            return URL(string:kBaseURL)!
        default:
            return URL(string:kBaseURL)!
        }
    }
    
    //1. 每个接口的相对路径
    //请求时的绝对路径是   baseURL + path
    var path: String {
        switch self {
        case .login:
            return "/login"
        case .getPageList:
            return "/mock/pages"
        case .getGroupPageList:
            return "/mock/groupPages"
        case .getContact:
            return "/mock/contacts"
        case .getWxMotionTops:
            return "/mock/wxMotionTops"
        case .getSimpleArrDic:
            return "/getSimpleArrDic"
        case let .other1(p1, p2, _, _):
            return "/list?id=(p1)&page=(p2)"
        case .other2:
            return ""
        }
    }

    //2. 每个接口要使用的请求方式
    var method: Moya.Method {
        switch self {
        case
                .getPageList,
                .getGroupPageList,
                .other1,
                .other2:
            return .get
        case
                .getContact,
                .getWxMotionTops,
                .getSimpleArrDic,
                .login:
            return .post
        }
    }

    //3. Task是一个枚举值,根据后台需要的数据,选择不同的http task。
    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 let .other1(_, _, p3, p4):
            params["p3"] = p3
            params["p4"] = p4
        default:
            //不需要传参数的接口走这里
            return .requestPlain
        }
        return .requestParameters(parameters: params, encoding: URLEncoding.default)
    }
    
}

使用

// Alamofire + Moya + SwiftyJSON
JhHttpTool.request(API.getPageList(1)) {[weak self] json in
    self?.mTextView.text = String(describing: JSON(json))
    JhAllLog(JSON(json))
} failure: {code, msg in
    JhLog("code : (code!)")
    JhLog("message : (msg)")
}

链式封装Alamofire

链式封装,API可以直接通过一个文件进行管理,里面只放url路径

//
//  JhRequest.swift
//  JhSwiftDemo
//
//  Created by Jh on 2021/12/28.
//  链式网络请求工具类:Alamofire + SwiftyJSON
import Foundation
import Alamofire
import SwiftyJSON

enum HttpRequestType {
    case get
    case post
}

public let JhRequest = NetworkKit.shared

// Networkkit属性设置
public class NetworkKit {
    
    public static let shared = NetworkKit()
    
    typealias SuccessHandlerType = ((JSON) -> Void)
    typealias FailureHandlerType = ((Int?, String) ->Void)
    
    private var requestType: HttpRequestType = .post//请求类型
    private var url: String?    // URL
    private var params: [String: Any]?  // 参数
    private var success: SuccessHandlerType?    // 成功的回调
    private var failure: FailureHandlerType?    // 失败的回调
    private var httpRequest: Request?
    
}

// NetworkKit属性的设置
extension NetworkKit{
    /// 设置url
    func url(_ url: String?) -> Self {
        self.url = url
        return self
    }
    
    /// 设置post/get 默认post
    func requestType(_ type:HttpRequestType) -> Self {
        self.requestType = type
        return self
    }
    
    /// 设置参数
    func params(_ params: [String: Any]?) -> Self {
        self.params = params
        return self
    }
    
    /// 成功的回调
    func success(_ handler: @escaping SuccessHandlerType) -> Self {
        self.success = handler
        return self
    }
    
    ///失败的回调
    func failure(handler: @escaping FailureHandlerType) -> Self {
        self.failure = handler
        return self
    }
    
}

// NetworkKit请求相关
extension NetworkKit{
    
    /// 发起请求 设置好相关参数后再调用
    func request() -> Void {
        var dataRequest: DataRequest?   // alamofire请求后的返回值
        
        // 发起请求
        if let URLString = url {
            ProgressHUD.show()
            let method = requestType == .get ? HTTPMethod.get : HTTPMethod.post
            dataRequest =  Alamofire.request(URLString, method: method, parameters: params)
            httpRequest = dataRequest
        }
        
        dataRequest?.responseJSON {
            (response) in
            ProgressHUD.hide()
            
            switch response.result {
            case let .success(response):
                do {
                    // *********** 这里可以统一处理错误码,弹出提示信息 ***********
                    let responseObject = JSON(response)
                    let code = responseObject["code"].intValue
                    let msg = String(describing: responseObject["msg"])
                    switch (code) {
                    case 200 :
                        // 数据返回正确
                        self.success?(responseObject)
                    case 401:
                        // 请重新登录
                        self.failure?(code,msg)
                        alertLogin(msg)
                    default:
                        // 其他错误
                        failureHandle(failure: self.failure, stateCode: code, message: msg)
                    }
                }
            case let .failure(error):
                failureHandle(failure: self.failure, stateCode: nil, message: error.localizedDescription)
            }
        }
        
        // 错误处理 - 弹出错误信息
        func failureHandle(failure: FailureHandlerType? , stateCode: Int?, message: String) {
            Alert.show(type: .error, text: message)
            failure?(stateCode ,message)
        }
        
        // 登录弹窗 - 弹出是否需要登录的窗口
        func alertLogin(_ title: String?) {
            // TODO: 跳转到登录页的操作:
        }
    }
    
    // 取消请求
    func cancel() {
        httpRequest?.cancel()
    }
    
}

使用

 // 链式网络请求:Alamofire + SwiftyJSON
 let url = kBaseURL + "/getSimpleArrDic"
 JhRequest.url(url).params([:]).requestType(.post).success { res in
     JhLog(" ========链式网络请求======== ")
     JhAllLog(res)