记录Swift网络请求模块封装

在使用Swift开发时,网络请求大多使用Alamofire,但使用时不是很方便,于是就计划封装下网络请求模块.
实现目标:
1.将通用参数和加密规则封装到底层,不要每次都填写;
2.将网络请求的通用处理;如发生请求时显示NetworkActivityIndicator等;
3.网络请求在调用时,尽可能简单,只需要关注需要使用的接口和必要的参数;
4.支持一些可能出现的额外处理.
调用的形式需要实现形如:

UserRouter.user(id:123).request() { result in
   print(result)
}

一. Routerable

首先将Request的header,params,url,method以及其他需要接口自定义的内容封装到protocol.由于header是一些固定的属性,所以不加入到协议中

// MARK: - Routerable
public protocol Routerable{
    // url, method, param, useCache
    var http : (String, RouterMethod, RouterParam, Bool) {get}
}

创建Routerable的extesion,将一些通用的属性设置出来,以下方法方便用于HttpManager调用

// MARK: - 验证相关的信息
extension Routerable {
    public var baseURL: String {
        return UserDefaults.hostName.value ?? ""
    }
    /// url
    public var url: String {
        return "\(baseURL)/\(http.0)"
    }
    /// method
    public var method: HTTPMethod {
        return http.1
    }
    /// 是否使用缓存
    public var useCache: Bool {
        return http.3
    }
    /// header
    public var headerFields: [String: String]{
        var header: [String:String] = [:]
        header["version"] = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
        header["type"] = "ios"
        return header
    }
    /// parameters
    public var parameters: Parameters {
        var newParams: Parameters = self.http.2
        //添加服务器版本
        newParams["version"] = "1.9"
        // sign验证=
        newParams["sign"] = sign(newParams)
        return newParams
    }
}

二. HttpManager

创建HttpManager用于管理HTTP请求

import Foundation
import Alamofire
import SwiftyJSON
import SVProgressHUD

// MARK: - HTTP请求管理
public final class HttpManager{
    private init(){}
    // 创建单例
    static let shared = HttpManager()
    // 缓存
    let urlCache = URLCache.init(memoryCapacity: 4*1024*1024, diskCapacity: 20*1024*1024, diskPath: nil)
    
    lazy var alamofireManager: SessionManager = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        let session = SessionManager(configuration: config)
        return session
    }()
}

在HttpManager中自定义请求方法

// MARK: - 基本网络请求
extension HttpManager{
    /// 通用网络请求
    ///
    /// - Parameters:
    ///   - router: 路由地址
    ///   - queue: 线程
    ///   - completionHandler: 完成的回调,isEnd用于处理带缓存的请求,一般情况会先返回缓存,等请求完成在返回真实数据
    public static func requestResult(router: Routerable, queue: DispatchQueue? = DispatchQueue.global(),isShowError: Bool = true, completionHandler: @escaping (Result<JSON>, Bool) -> Void) -> DataRequest{
        let request = shared.alamofireManager.request(router.url, method: router.method, parameters: router.parameters, encoding: URLEncoding.default, headers: router.headerFields)
        
        var isEnd = false
        if router.useCache {// 如果使用cache
            DispatchQueue.after(queue, 0.4, {
                if isEnd == false, let dataResponse = request.cacheRequest(router: router) {
                    LogInfo("*******\(router.url)使用缓存*******")
                    completionHandler(dataResponse.result, isEnd)
                }
            })
        }
        
        // 将要发送请求
        willSend(request)
        request.responseDict(router: router, queue: queue, completionHandler: { response in
            // 保存结果
            response.saveCache(router: router)
            // 请求结束
            isEnd = true
            // 完成请求返回
            completionHandler(response.result, isEnd)
            // 接收到服务端的结果
            didReceive(response, isShowError: isShowError)
        })
        return request
    }
}
// MARK: - Default处理
extension HttpManager {
    // MARK: - 网络指示器
    private static func NetworkActivityIndicatorVisible(_ isVisible: Bool){
        DispatchQueue.main.async {
            UIApplication.shared.isNetworkActivityIndicatorVisible = isVisible
        }
    }
    fileprivate static func willSend(_ request: DataRequest) {
        NetworkActivityIndicatorVisible(true)
    }
    fileprivate static func didReceive(_ response: DataResponse<JSON>, isShowError: Bool = true){
        NetworkActivityIndicatorVisible(false)
        LogInfo(response.debugDescription)
        if case let .failure(error) = response.result, isShowError{
            DispatchQueue.main.async {
                SVProgressHUD.showError(withStatus: error.localizedDescription)
            }
        }
    }
}

三.Alamofire+Custom
根据返回数据的格式,处理通用的错误返回,并打印详细的请求相关的数据用于调试.

import Foundation
import Alamofire
import SwiftyJSON

// MARK: - Alamofire扩展
extension DataRequest {
    @discardableResult
    public func responseDict(
        router: Routerable, queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<JSON>) -> Void) -> Self{
        
        response(queue: queue, responseSerializer: DataResponseSerializer { _, response, data, error in
            return DataRequest.dealResult(Request.serializeResponseData(response: response, data: data, error: error))
            }, completionHandler: { response in
                response.result.ifSuccess {
                    Helper.saveJSON(.http, response.result.value!, router.fileName)
                }
                completionHandler(response)
        })
        return self
    }
    
    class func dealResult(_ data: Result<Data>) -> Result<JSON> {
        var result: Result<JSON>! = nil
        switch  data{
        case .success(let jsonData):
            do {
                let json = try JSON(data: jsonData)
                let errCode = json["error"].intValue
                let message = json["message"].stringValue
                if errCode == 0 {
                    result = Result.success(json)
                }else{
                    let error = KFError.init(errorCode: errCode, message: message)
                    result = Result.failure(error)
                }
            } catch let error{
                result = Result.failure(error)
            }
        case .failure(let error):
            result =  Result.failure(error)
        }
        return result
    }
    
    /// 获取缓存
    ///
    /// - Parameter router: 请求路由
    /// - Returns: 返回结果
    func cacheRequest(router: Routerable) -> DataResponse<JSON>?  {
        if let request = self.request, request.httpMethod?.lowercased() == "get", let cachedReponse = HttpManager.shared.urlCache.cachedResponse(for: request){
            return DataResponse.init(request: request, response: (cachedReponse.response as! HTTPURLResponse), data: cachedReponse.data, result: DataRequest.dealResult(Result.success(cachedReponse.data)))
        }
        return nil
    }
}

extension DataResponse {
    
    /// 保存结果
    ///
    /// - Parameter router: 请求路由
    func saveCache(router: Routerable) {
        if router.useCache, let res = self.response, let data = self.data, let request = self.request, request.httpMethod?.lowercased() == "get" {// 使用cache的请求才需要缓存
            if self.result.isSuccess {
                HttpManager.shared.urlCache.storeCachedResponse(CachedURLResponse.init(response: res, data: data), for: request)
            }else{
                HttpManager.shared.urlCache.removeCachedResponse(for: request)
            }
        }
    }
    
    public var debugDescription: String {
        
        var output: [String] = []
        output.append("[Alamofire]")
        // Request
        output.append("[Request]:\(self.request?.description ?? "(invalid request)")")
        if let headers = self.request?.allHTTPHeaderFields {
            output.append("[Headers]:\(headers.description)")
        }
        if let bodyStream = self.request?.httpBodyStream {
            output.append("[BodyStream]:\(bodyStream.description)")
        }
        if let httpMethod = self.request?.httpMethod {
            output.append("[Method]:\(httpMethod)")
        }
        if let body = self.request?.httpBody, let stringOutput = String(data: body, encoding: .utf8) {
            output.append("[Body]:\(stringOutput)")
        }
        output.append("[Data]: \(data?.count ?? 0) bytes")
        output.append("[Time]: \(timeline.requestDuration)")
        if let data = data {
            do {
                output.append("[Result]: \(try JSON.init(data: data))")
            }catch {}
        }else{
            output.append("[Result]: \(result.value.debugDescription)")
        }
        return output.joined(separator: "\n")
    }
    private func JSONResponseDataFormatter(_ data: Data) -> Data {
        do {
            let dataAsJSON = try JSONSerialization.jsonObject(with: data)
            let prettyData =  try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
            return prettyData
        } catch {
            return data // fallback to original data if it can't be serialized.
        }
    }
}

三.UserRouter
封装关于User的网络请求

// MARK: - 用户请求
public enum UserRouter : Routerable{
    ///获取用户列表
    case userlist
    /// 获取user模型, id为用户id
    case user(id: Int)
    
    public var http: (String, RouterMethod, RouterParam, Bool){
        var path: String!
        let method: RouterMethod = .get
        var parameters: RouterParam = [:]
        var useCache = true
        
        switch self{
            case . userlist:
                path = "api/list"
            case let .user(id):
                path = "api/users/\(id)"
                parameters[ParamType.ticket_id.rawValue] = id
        }
        return (path, method, parameters, useCache)
    }
}

四.使用方式

HttpManager.requestResult(router: UserRouter.user(id: 123)) {(result, isEnd) in
   print(result)
}

或者给Routerable添加网络请求方法

extension Routerable {
    public func requestResult(queue: DispatchQueue? = DispatchQueue.global(),isShowError: Bool = true, completionHandler: @escaping (Result<JSON>, Bool) -> Void) -> DataRequest {
        return HttpManager.requestResult(router: self, queue: queue, isShowError: isShowError, completionHandler: completionHandler)
    }
}

这样也就可以直接通过router请求

UserRouter.user(id: 123).request() { ( result, isEnd) in
   print(result)
}

五.添加RxSwift支持

import Foundation
import RxSwift
import SwiftyJSON
import Alamofire
import SVProgressHUD

extension Routerable {
    var rx_request: Observable<JSON> {
       return HttpManager.rx_requestResult(self)
    }
}

// MARK: - HttpManager的Rx扩展
extension HttpManager {
    /// Rx版本的网络请求
    ///
    /// - Parameter router: 路由地址
    /// - Returns: 回调
    static func rx_requestResult(_ router: Routerable, isShowError: Bool = true) -> Observable<JSON>{
        return Observable.create({observer -> Disposable in
            let request = HttpManager.requestResult(router: router, completionHandler: { result, isEnd in
                switch result {
                case let .success(value):
                    observer.onNext(value)
                    if isEnd {
                      observer.onCompleted()
                    }
                case let .failure(error):
                    observer.onError(error)
                }
            })
            return Disposables.create {
                request.cancel()
            }
        })
    }
}

// MARK: - 添加新的Observable类型
extension ObservableType {
    /// 显示HUD
    func showHUD() -> Observable<Self.E> {
        return Observable.create { observer in
            // showHUD
            DispatchQueue.main.async {
                SVProgressHUD.show()
            }
            return self.subscribe({ event in
                    // hideHUD
                    DispatchQueue.main.async {
                        SVProgressHUD.dismiss()
                    }
                observer.on(event)
            })
        }
    }
    /// 绑定成功的值
    public func bindSuccess(to variable: Variable<E>) -> Disposable {
        return subscribe { event in
            switch event {
            case let .next(element):
                variable.value = element
            case .error(_):
                break
            case .completed:
                break
            }
        }
    }
    
    /// 当next或error时执行
    public func doOnce(on: @escaping ((E?) -> Void)) -> Observable<E> {
        return self.do(onNext: { (element) in
            on(element)
        }, onError: { (_) in
            on(nil)
        })
    }
    /// 便利返回数据
    public static func next(_ element: E) -> Observable<E> {
        return Observable.create({ (observer) -> Disposable in
            observer.onNext(element)
            observer.onCompleted()
            return Disposables.create()
        })
    }
    /// 网络请求
    func httpRequest(_ router: Routerable) -> Observable<JSON>{
        return Observable.create({ observer -> Disposable in
            var request: DataRequest? = nil
            let dispose = self.subscribe({ event in
                switch event {
                case .next(_):
                    request = HttpManager.requestResult(router: router, completionHandler: { result, isEnd in
                        switch result {
                        case let .success(value):
                            observer.onNext(value)
                            if isEnd {
                                observer.onCompleted()
                            }
                        case let .failure(error):
                            observer.onError(error)
                        }
                    })
                case let .error(error):
                    observer.onError(error)
                default:
                    break
                }
            })
            return Disposables.create {
                dispose.dispose()
                request?.cancel()
            }
        })
    }
}

github地址:SwiftHTTPManager

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,628评论 18 139
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,326评论 0 12
  • 安装: sudo apt-get install git-core 第一次使用配置:git config --gl...
    为梦想战斗阅读 466评论 0 0
  • 一 我的城 下雪了 而我还在暮秋沉睡 初冬的风有些微凉 轻轻敲打着我的门楣 思念的雨在天边漫洒 谁一直在相遇的季节...
    云飘碧天阅读 4,056评论 56 208
  • 我常在想,是什么绑住了我的双脚,让我停留在现状,思前想猛然发现是自己的懒惰,在捆绑自己的双脚让自己无法挪动,
    吃肉肉的兔兔阅读 140评论 0 0