阅读 341

基于Alamofire的网络封装

2019/12/16更新 修改重试逻辑 说明:虽然Alamofire内置了RequestRetrier协议帮助我们实现重试,但是呢,基于下载以及提交时,服务器获取到我们的提交的内容但是暂时无法处理的情况下,继续重试可能会造成错误,因此我们需要自己去对于重试的触发做实现,emmm....😊就好了

目的

我们作为开发,可能从一个项目到另一个项目的时候,避免不了要把一些通用的工具比如(UIKit,Foundation等一些扩展又要写一遍),那么网络操作也是如此。你可能会说:😳瓜,我使用的是三方库Alamofire,AFN...,但是你有没有意识到你的各种处理业务的代码中充斥着Alamofire./AFNetworking.,这不仅让你不爽,估计你的小伙伴也不会很开心。那么我们的目的就是对于三方库进行再一次封装,一方面尽量减少这种代码对于业务的影响(代码量),还有就是减少因为三方库的更新等对我们项目的影响,我们要做的只是修改我们封装的那部分代码 ###上菜之前的说明 封装的这个代码仅仅是结合本公司以及个人一点点经验做出来的,不足的地方希望大佬多提意见,后面我也会不足的地方提出来。

正菜之前的准备工作

swift语言开发整理的工具三方库,包括 字符串 日期 校验 http请求 网格菜单 列表菜单 钥匙串 本地图片浏览 在线图片浏览 设备信息 基础控制器封装(tableview scrollview collectionview wkwebview) 本地推送 基础视图(根据提供key value,生成view) 选择器 分页视图 seachcontroller+tableview AVPlayer 弹出的筛选框。 ❤️ 传送门 ❤️ 喜欢点个赞吧~

import Foundation
import Alamofire
import SwiftyJSON
//MARK:- 获取需要数据节点名称
public enum DataKey: String {
    case all = "all"
    case dataMap = "dataMap"
    case dataList = "dataList"
    case listDataMap = "listDataMap"
}
//MARK:- 错误码
//public enum ErrorCode{
    //200...299
   // case invalidResponse(String)
    //actionResult中的 ’success‘为false
   // case sysError(String)
    //其他错误 如 ‘failure’ 返回HTTP状态码 以及 错误信息描述
 //   case networkUnavailable(String?,Int?)
//}
//MARK:- 错误码
public enum ErrorCode{
    //200...299
    case invalidResponse(String)
    //actionResult中的 ’success‘为false
    case sysError(String)
    //其他错误 如 ‘failure’ 返回HTTP状态码 以及 错误信息描述
    case networkUnavailable(String?,Int?)
    //重试
    /** @param URLRequest  失败的request
     * @param NetWorkType 请求类型
     * @param DataKey     获取数据的参数
     * @param String      错误信息
     */
    case needRetrier(URLRequest,NetWorkType,DataKey?,String)
    /**
     * 上传失败 回调参数
     * @param String           请求地址
     * @param [String]         参数
     * @param JSON             整个数据
     * @param [Data]           数据信息
     * @param [String]         数据信息描述
     * @param String?          错误信息
     */
    case uploadError(String,[String],JSON,[Data],[String],String?)
}
public typealias Success<T> = (T) -> Void

public typealias Failure = (ErrorCode) -> Void
复制代码

2019/12/16修改 ErrorCode的枚举类型修改 重试需要我们拿到对应的request,对于表单提交我们需要拿到对应的参数 解释一下:上面的DataKey是后台返回的数据后我们想要的部分,比如我们只想要返回数据的dataMap里面的东西,那么我们就指定dataMap好了,这么做的好处是不需要一遍遍硬写这个参数,不足的地方是可能枚举不全,下面这个是错误码的处理,针对存在Alamofire中存在的错误,我们需要扔出去的东西,个人感觉这个地方处理的还不是很好。线面就是成功和失败的闭包,success<T>表明他返回的可能是任何类型,可能是Json,可能是Bool值等,看您心情。

###基本请求(不要觉得多/不要觉得多/不要觉得多 😝😝 )

public final class NetWorkTools {
    
    private init() {}
    
    static let shared = NetWorkTools()
    
    var sessionManger: SessionManager = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        let manger = Alamofire.SessionManager(configuration: config)
        return manger
    }()
    
}

extension NetWorkTools {
    /**
     * 基本请求方法
     * @param url             请求地址
     * @param param           参数
     * @param method          post get
     * @param dataKey         获取的数据部分 例如 data 下面的 ‘dataList’部分
     * @param headers         请求头
     * @param isNeedReConnect 需要重连 默认true 重连3次
     * @param success         成功回调
     * @param failure         失败回调
     */
    public static func getNormalRequestWith(url: String,
                                         param: Parameters,
                                         networkType:NetWorkType,
                                         method: HTTPMethod = .post,
                                         dataKey: DataKey = .all,
                                         success: @escaping Success<JSON>,
                                         failure: @escaping Failure) {
        
        var headers: [String:String] {
            if let headerValue = UserDefaults.standard.value(forKey: networkStaticHeaderKey) as? String {
                return [Authorization : headerValue]
            }
            return [:]
        }
        
        shared.sessionManger.request(url, method: method, parameters: param, encoding: URLEncoding.default, headers: headers)
            .validate() //200...299
            .responseJSON { (response) in

                switch response.result {
                case .success:
                    if let data = response.data{
                        let actionReuslt = JSON(data)[ConstantsHelp.actionReuslt]
                        if actionReuslt[ConstantsHelp.success].boolValue {
                            let json = JSON(data)[dataKey.rawValue]
                            //MARK:-存token
                            if  !JSON(data)[ConstantsHelp.dataMap][token].stringValue.isEmpty {
                                UserDefaults.standard.set(JSON(data)[ConstantsHelp.dataMap][token].stringValue, forKey: networkStaticHeaderKey)
                            }
                            success(json)
                        } else {
                            let message = (actionReuslt[ConstantsHelp.message].stringValue)
                            failure(.sysError(message))
                            
                        }
                    }
                case .failure(let error):
                    if let code = response.response?.statusCode {
                        if code == 500 || code == 502 || code == 503 || code == 504{
                            if let request = response.request {
                                failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                            }
                        } else {
                            failure(.sysError(error.localizedDescription))
                        }
                    } else {
                        failure(.sysError(error.localizedDescription))
                    }
                }
        }
    }
}
复制代码

针对Http状态码下的 500/502/503/504扔出来了重试,具体看自己的业务需求 基本的请求和下载/上传处理的错误时不同的 基本的请求和下载/上传处理的错误时不同的 基本的请求和下载/上传处理的错误时不同的 具体下面的全部代码贴出来了 可以自己看一下 ###前方高能 全部代码:


import Foundation
import Alamofire
import SwiftyJSON

//MARK: - headers 
let networkStaticHeaderKey = "topscommToken"
let Authorization = "Authorization"
let token = "token"

//MARK:- 获取需要数据节点名称
public enum DataKey: String {
    
    case all = "all"
    case dataMap = "dataMap"
    case dataList = "dataList"
    case listDataMap = "listDataMap"
    case actionResult = "actionResult"
    case none = "none"
}
//MARK:-网络请求类型
public enum NetWorkType {
    case normalRequest
    case upload
    case download
}
//MARK:- 错误码
public enum ErrorCode{
    //200...299
    case invalidResponse(String)
    //actionResult中的 ’success‘为false
    case sysError(String)
    //其他错误 如 ‘failure’ 返回HTTP状态码 以及 错误信息描述
    case networkUnavailable(String?,Int?)
    //重试
    /** @param URLRequest  失败的request
     * @param NetWorkType 请求类型
     * @param DataKey     获取数据的参数
     * @param String      错误信息
     */
    case needRetrier(URLRequest,NetWorkType,DataKey?,String)
    /**
     * 上传失败 回调参数
     * @param String           请求地址
     * @param [String]         参数
     * @param JSON             整个数据
     * @param [Data]           数据信息
     * @param [String]         数据信息描述
     * @param String?          错误信息
     */
    case uploadError(String,[String],JSON,[Data],[String],String?)
}

public typealias Success<T> = (T) -> Void

public typealias Failure = (ErrorCode) -> Void

public final class NetWorkTools {
    
    private init() {}
    
    static let shared = NetWorkTools()
    
    
    var sessionManger: SessionManager = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        let manger = Alamofire.SessionManager(configuration: config)
        return manger
    }()
    
}
extension NetWorkTools {
    /**
     * 基本请求方法
     * @param url             请求地址
     * @param param           参数
     * @param method          post get
     * @param dataKey         获取的数据部分 例如 data 下面的 ‘dataList’部分
     * @param headers         请求头
     * @param isNeedReConnect 需要重连 默认true 重连3次
     * @param success         成功回调
     * @param failure         失败回调
     */
    public static func getNormalRequestWith(url: String,
                                         param: Parameters,
                                         networkType:NetWorkType,
                                         method: HTTPMethod = .post,
                                         dataKey: DataKey = .all,
                                         success: @escaping Success<JSON>,
                                         failure: @escaping Failure) {
        
        var headers: [String:String] {
            if let headerValue = UserDefaults.standard.value(forKey: networkStaticHeaderKey) as? String {
                return [Authorization : headerValue]
            }
            return [:]
        }
        
        shared.sessionManger.request(url, method: method, parameters: param, encoding: URLEncoding.default, headers: headers)
            .validate() //200...299
            .responseJSON { (response) in

                switch response.result {
                case .success:
                    if let data = response.data{
                        let actionReuslt = JSON(data)[ConstantsHelp.actionReuslt]
                        if actionReuslt[ConstantsHelp.success].boolValue {
                            let json = JSON(data)[dataKey.rawValue]
                            //MARK:-存token
                            if  !JSON(data)[ConstantsHelp.dataMap][token].stringValue.isEmpty {
                                UserDefaults.standard.set(JSON(data)[ConstantsHelp.dataMap][token].stringValue, forKey: networkStaticHeaderKey)
                            }
                            success(json)
                        } else {
                            let message = (actionReuslt[ConstantsHelp.message].stringValue)
                            failure(.sysError(message))
                            
                        }
                    }
                case .failure(let error):
                    if let code = response.response?.statusCode {
                        if code == 500 || code == 502 || code == 503 || code == 504{
                            if let request = response.request {
                                failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                            }
                        } else {
                            failure(.sysError(error.localizedDescription))
                        }
                    } else {
                        failure(.sysError(error.localizedDescription))
                    }
                }
        }
    }

    /**
     * 基本上传方法
     * @param url             请求地址
     * @param keys            参数
     * @param parameters      整个数据
     * @param datasArr        数据信息
     * @param datasInfoArr    数据信息描述
     * @param isNeedReConnect 需要重连 默认true 重连3次
     * @param success         成功回调
     * @param failure         失败回调
     */
    public static func uploadRequestWith(url: String,
                                         keys: [String],
                                         parameters: JSON,
                                         datasArr:[Data],
                                         dataKey: DataKey = .actionResult,
                                         datasInfoArr:[String],
                                         networkType:NetWorkType,
                                         success: @escaping Success<JSON>,
                                         failure: @escaping Failure){
        var headers: [String:String] {
            if let headerValue = UserDefaults.standard.value(forKey: networkStaticHeaderKey) as? String {
                return [Authorization : headerValue]
            }
            return [:]
        }
        shared.sessionManger.upload(multipartFormData: { (multipartFormData) in
            //拼接数据
            var count = datasArr.count
            count = datasArr.count <= datasInfoArr.count ? datasArr.count:datasInfoArr.count
            for index in 0..<count {
                let data = datasArr[index]
                
                let withName = !VerifyHelp.checkImageInfo(imageName: datasInfoArr[index]) ? "withName" + String(index) + data.getImageFormat()!: datasInfoArr[index]
                let fileName = !VerifyHelp.checkImageInfo(imageName: datasInfoArr[index]) ? "fileName" + String(index) + data.getImageFormat()! : datasInfoArr[index]
                               
                multipartFormData.append(data, withName: withName, fileName: fileName, mimeType: "application/octet-stream")
            }
            if keys.count > 0{
                for value in keys{
                    let data:Data = parameters[value].stringValue.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
                    multipartFormData.append(data, withName:value)
                }
            }
            
            multipartFormData.append(URL(string:"sss")!, withName: "ss", fileName: "ssd", mimeType: "jpeg/jpg")
            
        }, to: url, headers: headers) { (request) in
            switch request {
            case .success(let upload, _ , _):
                upload.responseJSON { (response) in
                    //是否存在错误
                    if let error = response.error {
                        if let code = response.response?.statusCode {
                            if code == 500 || code == 502 || code == 503 || code == 504 || code == 404{
                                if let request = response.request {
                                    failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                                }
                            } else {
                                failure(.sysError(error.localizedDescription))
                            }
                        } else {
                            //offline 无状态码
                            failure(.networkUnavailable(error.localizedDescription, nil))
                        }

                    } else {
                        //成功
                        if let data = response.result.value as? [String : AnyObject] {
                            let actionResult = JSON(data)[ConstantsHelp.actionReuslt]
                            if  actionResult[ConstantsHelp.success].boolValue {
                                success(actionResult)
                            } else {
                                let message = (actionResult[ConstantsHelp.message].stringValue)
                                failure(.sysError(message))
                            }
                        }
                    }
                }
                //MARK:- 验证准备上传的数据是否合法
            case .failure:
                failure(.sysError("上传的数据不合法"))
            }
        }
    }
    /**
     * 基本下载方法
     * @param url             请求地址
     * @param isNeedReConnect 需要重连 默认true 重连3次
     * @param method          HTTPMethod
     * @param params          Parameters
     * @param headers         [String:String]
     * @param success         成功回调
     * @param failure         失败回调
     */
    public static func downloadFileWith(url: String,
                                        method: HTTPMethod = .post,
                                        dataKey: DataKey = .none,
                                        params: Parameters,
                                        networkType:NetWorkType,
                                        success: @escaping Success<String>,
                                        failure: @escaping Failure) {
        var headers: [String:String] {
            if let headerValue = UserDefaults.standard.value(forKey: networkStaticHeaderKey) as? String {
                return [Authorization : headerValue]
            }
            return [:]
        }
        
        let destination: DownloadRequest.DownloadFileDestination = { _, response in
            let documentsURL = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask)[0]
            let fileURL = documentsURL.appendingPathComponent(response.suggestedFilename!)
            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
        }
        shared.sessionManger.download(url, method: method, parameters: params, encoding: URLEncoding.default, headers: headers, to: destination).responseData { (response) in
            switch response.result {
            case .success:
                if let path = response.destinationURL?.path{
                    if path.hasSuffix("action") {
                        failure(.sysError("下载的文件不存在"))
                    } else {
                        success(path)
                    }
                }else {
                    if let error = response.error {
                        if let code = response.response?.statusCode {
                            if code == 500 || code == 502 || code == 503 || code == 504 ||  code == 504{
                                if let request = response.request {
                                    failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                                }
                            } else {
                                failure(.sysError(error.localizedDescription))
                            }
                        }
                    }
                }
            case .failure(let error):
                if let code = response.response?.statusCode {
                    if code == 500 || code == 502 || code == 503 || code == 504 ||  code == 504{
                        if let request = response.request {
                            failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                        }
                    } else {
                        failure(.sysError(error.localizedDescription))
                    }
                } else {
                    failure(.sysError(error.localizedDescription))
                }
            }
        }
    }
    
    //MARK:- 重试方法
    public static  func getRetrierRequest(request:URLRequest,
                                           dataKey: DataKey?,
                                           networkType: NetWorkType,
                                           success: @escaping Success<Any>,
                                           failure: @escaping Failure) {
        
        switch networkType {
        case .normalRequest:
            shared.sessionManger.request(request).validate().responseJSON { (response) in
                //在200...299之外
                if let error = response.error {
                    failure(.invalidResponse(error.localizedDescription))
                }
                switch response.result {
                case .success:
                    if let data = response.data{
                        let actionReuslt = JSON(data)[ConstantsHelp.actionReuslt]
                        if actionReuslt[ConstantsHelp.success].boolValue {
                            let json = JSON(data)[dataKey!.rawValue]
                            success(json)
                        } else {
                            let message = (actionReuslt[ConstantsHelp.message].stringValue)
                            failure(.sysError(message))
                        }
                    }
                case .failure(let error):
                    if let code = response.response?.statusCode {
                        if code == 500 || code == 502 || code == 503 || code == 504 {
                            if let request = response.request {
                                failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                            }
                        } else {
                            failure(.sysError(error.localizedDescription))
                        }
                    } else {
                        failure(.sysError(error.localizedDescription))
                    }
                }
            }
        case .download:
            let destination: DownloadRequest.DownloadFileDestination = { _, response in
                let documentsURL = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask)[0]
                let fileURL = documentsURL.appendingPathComponent(response.suggestedFilename!)
                return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
            }
            
            shared.sessionManger.download(request, to: destination).validate().responseData { (response) in
                switch response.result {
                case .success:
                    if let path = response.destinationURL?.path{
                        if path.hasSuffix("action") {
                            failure(.sysError("下载的文件不存在"))
                        } else {
                            success(path)
                        }
                    }else {
                        if let error = response.error {
                            if let code = response.response?.statusCode {
                                if code == 500 || code == 502 || code == 503 || code == 504 ||  code == 504{
                                    if let request = response.request {
                                        failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                                    }
                                } else {
                                    failure(.sysError(error.localizedDescription))
                                }
                            }
                        }
                    }
                case .failure(let error):
                    if let code = response.response?.statusCode {
                        if code == 500 || code == 502 || code == 503 || code == 504{
                            if let request = response.request {
                                failure(.needRetrier(request, networkType, dataKey, error.localizedDescription))
                            }
                        } else {
                            failure(.sysError(error.localizedDescription))
                        }
                    } else {
                        failure(.sysError(error.localizedDescription))
                    }
                }
            }
        default:
            break;
        }

复制代码