基于Alamofire封裝一套多功能的Swift版網(wǎng)絡(luò)庫(kù)

WXNetworkingSwift 在這里求關(guān)注

簡(jiǎn)介

有沒有遇到過這樣一種情況,每次在項(xiàng)目中使用請(qǐng)求庫(kù)去請(qǐng)求數(shù)據(jù)時(shí),各種小功能需要自己在每個(gè)請(qǐng)求里面單獨(dú)去開發(fā),比如請(qǐng)求緩存、請(qǐng)求HUD、設(shè)置請(qǐng)求頭、設(shè)置失敗重試機(jī)制、判斷是否請(qǐng)求成功、請(qǐng)求個(gè)性化打印日志、控制批量請(qǐng)求、頁(yè)面請(qǐng)求重復(fù)寫數(shù)據(jù)轉(zhuǎn)模型......, 甚至使用了很久的第三方網(wǎng)絡(luò)某一天不維護(hù)了,導(dǎo)致項(xiàng)目那里面每個(gè)頁(yè)面到處直接使用的Api更換起來簡(jiǎn)直就是災(zāi)難,面對(duì)這種情況特意 底層基于Alamofire庫(kù) 封裝一套支持高度自定義擴(kuò)展多功能的網(wǎng)絡(luò)請(qǐng)求庫(kù),幫我們?cè)陂_發(fā)時(shí)省去了很多配置,也可以增加自定義插件使,即使以后更換底層請(qǐng)求庫(kù)也很方便,后續(xù)也會(huì)不斷維護(hù)更新各種小功能,目前支持的主要功能如下: OC版本的見這里

功能列表:

  • 1、自定義請(qǐng)求頭;簡(jiǎn)單配置請(qǐng)求頭或加密頭

  • 2、自動(dòng)處理是否緩存;設(shè)置緩存機(jī)制,自動(dòng)失效時(shí)間等

  • 3、請(qǐng)求失敗自定義多次重試;支持失敗后每隔幾秒嘗試再試請(qǐng)求,如啟動(dòng)App后一定要請(qǐng)求的必要數(shù)據(jù)接口。

  • 4、支持上傳接口抓包日志;如上傳到公司內(nèi)部日志服務(wù)器系統(tǒng)上,供測(cè)試人員排查問題或快速抓包排查問題。

  • 5、極簡(jiǎn)上傳下載文件監(jiān)聽; 簡(jiǎn)單配置監(jiān)聽上傳下載文件進(jìn)度。

  • 6、支持全局/單個(gè)配置請(qǐng)求成功后keyPath模型映射;頁(yè)面上無需每個(gè)接口編寫解析字典轉(zhuǎn)模型的重新代碼,支持?jǐn)?shù)組和自定義模型;

  • 7、約定全局請(qǐng)求的提示Hud ToastKey;支持單個(gè)配置或全局配置請(qǐng)求失敗時(shí)的HUD Toast自動(dòng)彈框提示。

  • 8、請(qǐng)求遇到相應(yīng)Code時(shí)觸發(fā)通知;如:Token失效全部重新登錄等;

  • 9、網(wǎng)絡(luò)請(qǐng)求過程多鏈路回調(diào)管理;如:請(qǐng)求將要開始回調(diào),請(qǐng)求回調(diào)將要停止,請(qǐng)求已經(jīng)回調(diào)完成;

  • 10、格式化打印網(wǎng)絡(luò)日志;輸出日志一目了然,如:請(qǐng)求接口地址、參數(shù)、請(qǐng)求頭、耗時(shí)、響應(yīng);

  • 11、批量請(qǐng)求;支持自定義每個(gè)請(qǐng)求的所有配置,并且可配置等待全部完成才回調(diào)還是一起完成才回調(diào);

  • 12、支持debug模式不請(qǐng)求網(wǎng)絡(luò)快速調(diào)試模擬接口響應(yīng)數(shù)據(jù);如:本地json string,Dictionary,local json file(僅限模擬器調(diào)試), http test url

    . . . . . .(持續(xù)完善-ing)

使用環(huán)境

iOS, swift 5.0

安裝方式

WXNetworkingSwift is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'WXNetworkingSwift'

用法方法見 (示例Demo

可靈活配置的基礎(chǔ)請(qǐng)求對(duì)象

///請(qǐng)求基礎(chǔ)對(duì)象, 外部上不建議直接用,請(qǐng)使用子類請(qǐng)求方法
open class WXBaseRequest: NSObject {
    ///請(qǐng)求Method類型
    fileprivate (set) var requestMethod: HTTPMethod = .post
    ///請(qǐng)求地址
    fileprivate (set) var requestURL: String = ""
    ///請(qǐng)求參數(shù)
    fileprivate var parameters: WXDictionaryStrAny? = nil
    ///請(qǐng)求超時(shí),默認(rèn)30是
    public var timeOut: TimeInterval = 30
    ///請(qǐng)求自定義頭信息
    public var requestHeaderDict: Dictionary<String, String>? = nil
    ///請(qǐng)求序列化對(duì)象 (json, form表單)
    public var requestSerializer: WXRequestSerializerType = .EncodingJSON
    ///請(qǐng)求任務(wù)對(duì)象
    fileprivate var requestDataTask: Request? = nil
    
    ///請(qǐng)求方法見源碼
}

可靈活配置的單個(gè)請(qǐng)求對(duì)象:


/// 單個(gè)請(qǐng)求對(duì)象, 功能根據(jù)需求可多種自定義
open class WXRequestApi: WXBaseRequest {
    
    ///請(qǐng)求成功時(shí)是否自動(dòng)緩存響應(yīng)數(shù)據(jù), 默認(rèn)不緩存
    public var autoCacheResponse: Bool = false
    
    ///自定義請(qǐng)求成功時(shí)的緩存數(shù)據(jù), (返回的字典為此次需要保存的緩存數(shù)據(jù), 返回nil時(shí)底層則不緩存)
    public var cacheResponseBlock: ( (WXResponseModel) -> (WXDictionaryStrAny?) )? = nil
    
    ///自定義解析成功時(shí)的響應(yīng)數(shù)據(jù), (例如: 在請(qǐng)求成功后 需要解密響應(yīng)的json結(jié)果后才能真正獲取成功標(biāo)識(shí), 解析模型等等..)
    public var decryptHandlerResponse: ((AnyObject) -> AnyObject)? = nil
    
    ///自定義請(qǐng)求成功映射Key/Value, (key可以是KeyPath模式進(jìn)行匹配 如: data.status)
    ///注意: 每個(gè)請(qǐng)求狀態(tài)優(yōu)先使用此屬性判斷, 如果此屬性值為空, 則再取全局的 WXNetworkConfig.successStatusMap的值進(jìn)行判斷
    public var successStatusMap: (key: String, value: String)? = nil

    ///請(qǐng)求成功時(shí)自動(dòng)解析數(shù)據(jù)模型映射:Key/ModelType, (key可以是KeyPath模式進(jìn)行匹配 如: data.returnData)
    ///成功解析的模型在 WXResponseModel.parseKeyPathModel 中返回
    public var parseModelMap: (parseKey: String, modelType: Convertible.Type)? = nil
    
    ///times: 請(qǐng)求失敗之后重新請(qǐng)求次數(shù), delay: 每次重試的間隔
    public var retryWhenFailTuple: (times: Int, delay: Double)? = nil
    
    /// [??僅DEBUG模式生效??] 作用:方便開發(fā)時(shí)調(diào)試接口使用,設(shè)置的值可為以下4種:
    /// 1. json String: 則不會(huì)請(qǐng)求網(wǎng)絡(luò), 直接響應(yīng)回調(diào)此json值
    /// 2. Dictionary: 則不會(huì)請(qǐng)求網(wǎng)絡(luò), 直接響應(yīng)回調(diào)此Dictionary值
    /// 3. local file path: 則直接讀取當(dāng)前本地的path文件內(nèi)容 (僅限模擬器調(diào)試)
    /// 4. http(是) path: 則直接請(qǐng)求當(dāng)前設(shè)置的path
    public var debugJsonResponse: Any? = nil

    ///請(qǐng)求轉(zhuǎn)圈的父視圖
    public var loadingSuperView: UIView? = nil
    
    ///上傳文件Data數(shù)組
    public var uploadFileDataArr: [ Data ]? = nil
    
    ///自定義上傳時(shí)包裝的數(shù)據(jù)Data對(duì)象
    public var uploadConfigDataBlock: ( (MultipartFormData) -> Void )? = nil
    
    ///監(jiān)聽上傳/下載進(jìn)度
    public var fileProgressBlock: WXProgressBlock? = nil
    
    ///網(wǎng)絡(luò)請(qǐng)求過程多鏈路回調(diào)<將要開始, 將要停止, 已經(jīng)完成>
    /// 注意: 如果沒有實(shí)現(xiàn)此代理則會(huì)回調(diào)單例中的全局代理<globleMulticenterDelegate>
    public var multicenterDelegate: WXNetworkMulticenter? = nil
    
    ///可以用來添加幾個(gè)accossories對(duì)象 來做額外的插件等特殊功能
    ///如: (請(qǐng)求HUD, 加解密, 自定義打印, 上傳統(tǒng)計(jì))
    public var requestAccessories: [WXNetworkMulticenter]? = nil
    
    ///請(qǐng)求方法見源碼
}

請(qǐng)求響應(yīng)對(duì)象的豐富信息

//MARK: - 請(qǐng)求響應(yīng)對(duì)象

///包裝的響應(yīng)數(shù)據(jù)
public class WXResponseModel: NSObject {
    /**
     * 是否請(qǐng)求成功,優(yōu)先使用 WXRequestApi.successStatusMap 來判斷是否成功
     * 否則使用 WXNetworkConfig.successStatusMap 標(biāo)識(shí)來判斷是否請(qǐng)求成功
     ***/
    public var isSuccess: Bool = false
    ///本次響應(yīng)Code碼
    public var responseCode: Int? = nil
    ///本次響應(yīng)的提示信息 (頁(yè)面可直接用于Toast提示,
    ///如果接口有返回messageTipKeyAndFailInfo.tipKey則會(huì)取這個(gè)值, 如果沒有返回則取defaultTip的默認(rèn)值)
    public var responseMsg: String? = nil
    ///本次數(shù)據(jù)是否為緩存
    public var isCacheData: Bool = false
    ///請(qǐng)求耗時(shí)(毫秒)
    public var responseDuration: TimeInterval? = nil
    ///解析數(shù)據(jù)的模型: 可KeyPath匹配, 返回 Model對(duì)象 或者數(shù)組模型 [Model]
    public var parseKeyPathModel: AnyObject? = nil
    ///本次響應(yīng)的原始數(shù)據(jù): NSDictionary/ UIImage/ NSData /...
    public var responseObject: AnyObject? = nil
    ///本次響應(yīng)的原始字典數(shù)據(jù)
    public var responseDict: WXDictionaryStrAny? = nil
    ///本次響應(yīng)的數(shù)據(jù)是否為Debug測(cè)試數(shù)據(jù)  (本地電腦路徑時(shí)僅限模擬器調(diào)試)
    public var isDebugResponse: Bool = false
    ///失敗時(shí)的錯(cuò)誤信息
    public var error: NSError? = nil
    ///原始響應(yīng)
    public var urlResponse: HTTPURLResponse? = nil
    ///原始請(qǐng)求
    public var urlRequest: URLRequest? = nil
}

可靈活配置的批量請(qǐng)求對(duì)象:

///批量請(qǐng)求對(duì)象, 可以
open class WXBatchRequestApi {
    
    ///全部請(qǐng)求是否都成功了
    public var isAllSuccess: Bool = false
    
    ///全部響應(yīng)數(shù)據(jù), 按請(qǐng)求requestArray的Api添加順序排序返回
    public var responseDataArray: [WXResponseModel] = []
    
    ///全部請(qǐng)求對(duì)象, 響應(yīng)時(shí)Api按添加順序返回
    fileprivate var requestArray: [WXRequestApi]
    ///請(qǐng)求轉(zhuǎn)圈的父視圖
    fileprivate (set) var loadingSuperView: UIView? = nil
    
    ///請(qǐng)求方法見源碼
}

1.單個(gè)請(qǐng)求示例

func testRequest() {
        let url = "http://123.207.32.32:8000/home/multidata"
        let api = WXRequestApi(url, method: .get)
        api.timeOut = 40 //設(shè)置超時(shí)時(shí)間
        api.loadingSuperView = view //請(qǐng)求loading HUD
        api.autoCacheResponse = true //是否需要緩存
        api.requestHeaderDict = [:] //設(shè)置請(qǐng)求自定義頭信息
        api.successStatusMap = (key: "returnCode",  value: "SUCCESS") //設(shè)置請(qǐng)求成功標(biāo)識(shí)key(支持keyPath)
        api.parseModelMap = (parseKey: "data.dKeyword", modelType: DKeywordModel.self)  //設(shè)置請(qǐng)求成功模型解析(支持keyPath)
        api.retryWhenFailTuple = (times: 3, delay: 2.0) //設(shè)置請(qǐng)求失敗重試機(jī)制
        api.multicenterDelegate = self //網(wǎng)絡(luò)請(qǐng)求過程多鏈路回調(diào)<將要開始, 將要停止, 已經(jīng)完成>
        
        //設(shè)置自定義解析成功時(shí)的響應(yīng)數(shù)據(jù)
        api.decryptHandlerResponse = { (response: AnyObject) -> AnyObject in
            // 自定義解析數(shù)據(jù)
        }
        //設(shè)置自定義請(qǐng)求成功時(shí)的緩存數(shù)據(jù)
        api.cacheResponseBlock = { WXResponseModel -> WXDictionaryStrAny? in
            //自定義緩存
        }
        
        requestTask = api.startRequest { [weak self] responseModel in
            if responseModel.isSuccess {
                self?.textView.text = responseModel.parseKeyPathModel?.description
            } else {
                self?.textView.text = responseModel.responseMsg
            }
        }
    }

2.批量請(qǐng)求示例

func testBatchRequest() {
        let url1 = "https://httpbin.org/get"
        let api1 = WXRequestApi(url1, method: .get)
        api1.autoCacheResponse = true
        
        let url2 = "https://httpbin.org/delay/5"
        let para2: [String : Any] = ["name" : "張三"]
        let api2 = WXRequestApi(url2, method: .get, parameters: para2)
        
        let api = WXBatchRequestApi(apiArray: [api1, api2], loadingTo: view)
        api.startRequest({ [weak self] batchApi in
            print("批量請(qǐng)求回調(diào)", batchApi.responseDataArray)
            self?.textView.text = batchApi.responseForRequest(request: api1)?.responseDict?.description
            
        }, waitAllDone: true)
    }

3.Json請(qǐng)求解析模型示例

func testParseModel() {
        let url = "http://app.u17.com/v3/appV3_3/ios/phone/comic/boutiqueListNew"
        let param: [String : Any] = ["sexType" : 1]

        let api = WXRequestApi(url, method: .get, parameters: param)
//        api.debugJsonResponse = "http://10.8.41.162:8090/app/activity/page/detail/92546"  //http( s ) test URL
//        api.debugJsonResponse = "/Users/xinGe/Desktop/test.json"   // (僅限模擬器調(diào)試)                      //Desktop json file
//        api.debugJsonResponse = "test.json"                                               //Bundle json file
//        api.debugJsonResponse = ["code" : "1", "data" : ["message" : "測(cè)試字典"]]          //Dictionary Object
//        api.debugJsonResponse =
//"""
//        {"data":{"message":"成功","stateCode":1,"returnData":{"galleryItems":[],"comicLists":[{"comics":[{"subTitle":"少年 搞笑","short_description":"突破次元壁的漫畫!","is_vip":4,"cornerInfo":"190","comicId":181616,"author_name":"壁水羽","cover":"https://cover-oss.u17i.com/2021/07/12647_1625125865_1za73F2a4fD1.sbig.jpg","description":"漫畫角色發(fā)現(xiàn)自己生活在一個(gè)漫畫的籠子里,于是奮起反抗作者,面對(duì)角色的不配合,作者不得已要不斷更改題材,恐怖,魔幻,勵(lì)志輪番上陣,主角們要一一面對(duì),全力通關(guān)","name":"籠中人","tags":["少年","搞笑"]}],"comicType":6,"sortId":"86","newTitleIconUrl":"https://image.mylife.u17t.com/2017/07/10/1499657929_N7oo9pPOhaYH.png","argType":3,"argValue":8,"titleIconUrl":"https://image.mylife.u17t.com/2017/08/29/1503986106_7TY5gK000yjZ.png","itemTitle":"強(qiáng)力推薦作品","description":"更多","canedit":0,"argName":"topic"}],"textItems":[],"editTime":"0"}},"code":1}
//"""

        api.timeOut = 40
        api.loadingSuperView = view
        api.autoCacheResponse = false
        api.retryWhenFailTuple = (times: 3, delay: 1.0)
        api.successStatusMap = (key: "code", value: "1")
        // api.parseModelMap = (parseKey: "data.returnData.comicLists", modelType: ComicListModel.self)

        requestTask = api.startRequest { [weak self] responseModel in
            self?.textView.backgroundColor = .groupTableViewBackground
            if let rspData = responseModel.responseObject as? Data {
                if let image = UIImage(data: rspData) {
                    self?.textView.backgroundColor = .init(patternImage: image)
                }
            }
        }
    }

4.上傳文件示例

func testUploadFile() {
        let url = "http://10.8.31.5:8090/uploadImage"
        let param = [
            "appName" : "TEST",
            "platform" : "iOS",
            "version" : "7.3.3",
        ]
        let api = WXRequestApi(url, method: .post, parameters: param)
        api.loadingSuperView = view
        api.retryWhenFailTuple = (times: 3, delay: 3.0)
        api.successStatusMap = (key: "code", value: "200")
        
        let image = UIImage(named: "womenPic")!
        let imageData = UIImagePNGRepresentation(image)
        
        api.uploadFileDataArr = [imageData!]
        api.uploadConfigDataBlock = { multipartFormData in
            multipartFormData.append(imageData!, withName: "files", fileName: "womenPic.png", mimeType: "image/png")
        }
        api.fileProgressBlock = { progress in
            let total = Float(progress.totalUnitCount)
            let completed = Float(progress.completedUnitCount)
            let percentage = completed / total * 100
            print("上傳進(jìn)度: \(String(format:"%.2f",percentage)) %")
        }
        requestTask = api.uploadFile { [weak self] responseModel in
            if responseModel.isSuccess {
                self?.textView.backgroundColor = .init(patternImage: image)
            }
        }
    }

5.下載文文件示例

func testDownloadFile() {
        //壓縮包
        var  url = "http://i.gtimg.cn/qqshow/admindata/comdata/vipThemeNew_item_2135/2135_i_4_7_i_1.zip"
        //視頻: (來源于: http://sp.jzsc.net)
        url = "http://down1.jzsc.net//sp/video/2019-06-22/d4fe4a94-0c21-4c99-ad35-2bdb23ab4de9.mp4"
        //圖片
        url = "https://picsum.photos/375/667?random=1"
        
        let api = WXRequestApi(url, method: .get, parameters: nil)
        api.loadingSuperView = view
        
        api.fileProgressBlock = { progress in
            let total = Double(progress.totalUnitCount)
            let completed = Double(progress.completedUnitCount)
            let percentage = completed / total * 100.0
            print("下載進(jìn)度: \(String(format:"%.2f",percentage)) %")
        }
        requestTask = api.downloadFile { [weak self] responseModel in
            if let rspData = responseModel.responseObject as? Data {
                if let image = UIImage(data: rspData) {
                    self?.textView.backgroundColor = .init(patternImage: image)
                }
                if var mimeType = responseModel.urlResponse?.mimeType {
                    mimeType = mimeType.replacingOccurrences(of: "/", with: ".")
                    let url = URL(fileURLWithPath: "/Users/xin610582/Desktop/" + mimeType, isDirectory: true)
                    try? rspData.write(to: url)
                }
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容