2019/12/16 Updated The retry logicNote: Although Alamofire has the built-in Requester protocol to help us achieve retry, however, it may cause errors to continue to retry based on the situation that the server gets our submitted content during download and submit but cannot process it for the time being, so we need to implement the retry trigger ourselves. emmm.... 😊

purpose

As developers, we may have to write some generic tools (UIKit,Foundation, etc.) as we move from project to project, and the same is true for web operations. You might say: 😳 melon, I’m using the tripartite library Alamofire,AFN… , but are you aware that your code is filled with all kinds of processing business Alamofire. / AFNetworking., this not only make you happy, estimates that your friend won’t be happy. So our goal is to encapsulate the tripartite library again, on the one hand, to minimize the impact of such code on the business (code volume), and to reduce the impact of the update of the tripartite library on our project. What we need to do is to modify the part of the code we encapsulated ### before serving the food. The code encapsulated is only made by combining our company and personal experience. I hope you can give more suggestions on the deficiencies, and I will also put forward the deficiencies later.

The preparation before the main course

Swift language development and collation tools tripartite library, Includes string date check HTTP request grid menu list menu Key string Local picture browse online picture browse device information basic controller Encapsulation (TableView ScrollView CollectionView wkWebView) Local push Base view (based on the key value provided, generate view) selector Paging view seachController + TableView AVPlayer popup filter box. ❤️ portal ❤️ give it a thumbs up

Import Foundation import Alamofire import SwiftyJSON //MARK:- Obtain required data node name Public enum DataKey: String { case all = "all" case dataMap = "dataMap" case dataList = "dataList" case listDataMap = "listDataMap" } ErrorCode //public enum ErrorCode{//200... 299 // Case invalidResponse(String) //actionResult 'success' is false // case sysError(String) // Other errors such as' failure ' Return the HTTP status code and error message description // case networkUnavailable(String? ,Int?) //MARK:- ErrorCode public enum ErrorCode{//200... 299 Case invalidResponse(String) // 'Success' in actionResult is false Case sysError(String) // Other errors such as' failure 'return HTTP status code and Error Message Description Case networkUnavailable(String? ,Int?) // Retry /** @param URLRequest Failed request * @param NetWorkType Request type * @param DataKey Parameter for obtaining data * @param String Error message */ case needRetrier(URLRequest,NetWorkType,DataKey? ,String) /** * Upload failed callback parameter * @param String Request address * @param [String] parameter * @param JSON entire Data * @param [Data] Data information * @param [String] Data Information Description * @param String? */ case uploadError(String,[String],JSON,[Data],[String],String?) } public typealias Success<T> = (T) -> Void public typealias Failure = (ErrorCode) -> VoidCopy the code

2019/12/16 Modify the enumeration type of ErrorCode. Retry requires that we get the corresponding request. For form submission, we need to get the corresponding parameters. The DataKey above is the part of the data that we want to return from the background. For example, we only want the contents of the dataMap that returns the data, so we specify dataMap. The advantage of this is that we do not have to write this parameter repeatedly. For the bugs in Alamofire, the things we need to throw out, personally feel that this place is not handled very well. Lines and planes are closures of success and failure. Success

indicates that it can return any type, Json, Bool, etc., depending on your mood.

### Basic request (don’t feel too much/Don’t feel too much/Don’t feel too much 😝😝)

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 {/** * basic request method * @param URL request address * @param param parameter * @param method Post get * @param dataKey Data part such as' dataList 'part of data * @param headers request header * @param isNeedReConnect Requires reconnection default true Public static func getNormalRequestWith(url: String, param:) 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:- save 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)) } } } } }Copy the code

For Http status code 500/502/503/504 thrown out retry, The following code is posted and you can have a look at it for yourself

###


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;
        }

Copy the code