Alamofire 、ObjectMapper封装

PYNetManager.gif

可以在debug模式下,可以选择测试url
demo点这里

简介:

  1. 实现统一设置:超时时长、header、对code码的处理。。
  2. 统一对url进行了转码。(空格等特殊字符不崩溃)
  3. 使用简单,自动转化成为对象。(array,object,json)
  4. 对请求结果的清晰打印。(分为正确、错误,只有在debug模式会打印,可以在AlamafireMenager_Configuration中配置是否打印)

结构:

  1. AlamafireMenager_Configuration.swift
    对一些公共信息的配置
  2. AlamofireMenager.swift
    对外暴露请求的接口
  3. AlamofireSession.swift
    对SessionManager的封装
  4. RequestMenager.swift
    生成了request,(分为loadDataRequest与updataReqeust)
    KRURLMenager
    对url的处理
  5. RespnseCodeMenager.swift
    打印了请求出的信息。(成功,失败),可以继承自这个类自定义处理code

封装思路

全局的配置

///一些统一的配置

import UIKit

///域名 配置
let baseURL = "http://api.dianping.com/"

//MARK: - code 的处理

///code 处理 是否打印Log日志
let isPrintSucceedNetWorkLog: Bool = true
///是否打印失败请求
let isPrintErrorNetWorkLog: Bool = true
///是否打印请求成功后的数据
let isPrintSucceedData: Bool = isDebug
///code处理的类 更改这里 全局配置code 的处理类
let k_codeMenager: RespnseCodeMenager.Type = KRCodeHandler.self


//MARK: - 超时时间
///超时时间
let Alamafire_TimeoutIntervalForRequest:TimeInterval = 10


//MARK: - 所有请求都会带的东西比如 版本和 cookie
var Alamofire_header: [String:String]? {
    get {
        return [
            "Version": KR_Version
        ]
    }
}
private var versionPrivate: String?
var KR_Version: String {
    get {
        if let versionPrivate_ = versionPrivate {
            return versionPrivate_
        }
        versionPrivate = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)
        return versionPrivate ?? "没有version😁"
    }
}


/**
 * log 在release 版本不打印
 * 注意要在 项目的 budSeting中 查找 `Other Swift Flags`,修改debug模式的flag 为“DEBUG”
 */
func dPrint(_ item: @autoclosure () -> Any) {
    if isDebug {
        print(item())
    }
}

///是否为debug模式
var isDebug: Bool {
    get {
        #if DEBUG
            return true
            #else
            return false
        #endif
    }
}

1. 对url的封装

KRURLMenager: 对url的path进行了一个特殊字符的处理,并返回一个不可选类型的URL

class KRURLMenager: NSObject {
    
    static let baseURLString = baseURL
    
    private class func getBaseURLStr(_ str: String) -> (String) {
        return KRURLMenager.baseURLString + str
    }
    ///返回一个url 并且 cach处理
    class func getURL(_ path:String) throws -> URL {
        var urlStr = KRURLMenager.getBaseURLStr(path)
        urlStr = urlStr.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
        guard let URL = URL(string: urlStr) else { throw AFError.invalidURL(url: urlStr) }
        return URL
    }
}

2.对Request的封装

RequestMenager: 要把全局的header传入到Request中,且做了特殊字符的处理,
默认生成了全局的配置参数对象,根据传入的参数来拼接url参数,最后返回一个DataRequest

/// qury 参数
private let query_Parameter = URLEncoding.init(destination: .queryString)

/// body 参数
private let httpBody_Parameter = URLEncoding.init(destination: .httpBody)
 //MARK: - downLoad Request
    
    /// 获取 下载请求
    ///
    /// - Parameters:
    ///   - path: url
    ///   - HTTPMethod_: 请求方式
    ///   - parameters: 请求参数
    ///   - parametersType: 请求参数 拼接类型
    /// - Returns: DataRequest
    class func getDataRequest(Path path: String,HTTPMethod HTTPMethod_: HTTPMethod? = .get,_ parameters: [String:Any]? = nil,_ parametersType: ParamaetersType? = nil) ->(DataRequest?) {
        let request = RequestMenager.getURLRequest(Path: path, HTTPMethod: HTTPMethod_, parameters, parametersType)
        if let request = request {

            return AlamofireSession.default.sessionMenager.request(request)
        }
        return nil
    }
   
    class func getURLRequest(Path path: String,HTTPMethod HTTPMethod_: HTTPMethod? = .get,_ parameters: [String:Any]? = nil,_ parametersType: ParamaetersType? = nil) -> (URLRequest?){
      
        do{
            let url = try KRURLMenager.getURL(path)
            var requst = URLRequest.init(url: url)
            requst.httpMethod = (HTTPMethod_ ?? .get).rawValue
            
            ///传入 一些全局header 比如
            for (value,key) in Alamofire_header ?? Dictionary() {
                requst.setValue(value, forHTTPHeaderField: key)
            }
            
            //传入版本  "Version": "2.1.0"
            switch parametersType ?? .query{
                
            case .query:
                return try query_Parameter.encode(requst, with: parameters)
                
            case .body:
                return try httpBody_Parameter.encode(requst, with: parameters)
            }
        } catch {
            dPrint("🌶\n 数据下载 request 转化失败 " + path + "🌶\n")
            return nil
        }
    }

3.对 SessionManager 的封装

AlamofireSession:对超时时间修改

class AlamofireSession: NSObject {
    static let `default`: AlamofireSession = AlamofireSession()
    /// 请求数据的 Menager
     var sessionMenager: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = TimeInterval(Alamafire_TimeoutIntervalForRequest)
        
        let sessionMenager = SessionManager.init(configuration: configuration, delegate: AlamofireSessionDelegate(), serverTrustPolicyManager: nil)
        
        return sessionMenager
    }()
}

4. 对 Mappable 的封装

AlamofireMenagerMap遵循了Mappable协议
内部实现了func mapping(map: Map),用到了运行时,获取了属性名,并调用了相应的map方法。
从而实现了继承自AlamofireMenagerMap的model,不再需要写func mapping(map: Map)方法

import UIKit
import ObjectMapper
class AlamofireMenagerMap: NSObject, Mappable {
    ///已经key对应的属性将要赋值
    private var setingValueCallBack: ((_ key:String,_ value: AnyObject)->())?
    
    ///已经key对应的属性已经赋值
    private var setedValueCallBack: ((_ key:String,_ value: AnyObject)->())?
   
    ///已经key对应的属性将要赋值
    func setingValue(_ callBack: @escaping (_ key:String,_ value: AnyObject)->()?){
        setingValueCallBack = callBack as? ((String, AnyObject) -> ())
    }
    ///已经key对应的属性已经赋值
    func setedValue(_ callBack: @escaping (_ key:String,_ value: AnyObject)->()?){
        setedValueCallBack = callBack as? ((String, AnyObject) -> ())
    }

    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        let propertyNames = self.getPropertyNames()
        for key in propertyNames {
            print(key)
            var property = value(forKey: key)
            property <- map[key]
            setingValueCallBack?(key,property as AnyObject)
            setValue(property, forKey: key)
            setedValueCallBack?(key,property as AnyObject)
        }
    }   
}

5.动态获取对象的 propertyNames

AlamofireGetProperty.swift


extension NSObject {
    
    func getPropertyNames() -> ([String]){
        
        var outCount:UInt32
        
        outCount = 0
        
        let propers = class_copyPropertyList(self.classForCoder, &outCount)!
        
        
        
        let count:Int = Int(outCount);
        
        print("共有\(outCount)个")
        var propertyArray = [String]()
        for i in 0...(count-1) {
            
            let aPro: objc_property_t = propers[i]!
            
            let proName:String! = String.init(utf8String: property_getName(aPro))
            
            propertyArray.append(proName)
        }
        return propertyArray
    }
}

使用

RespnseCodeMenager.swift

import UIKit
import Alamofire
import AlamofireObjectMapper
import ObjectMapper


public class RespnseCodeMenager: NSObject {
    ///继承这个这个类,并且 重写这个函数 来处理 code
    class func custom_handCodeFunc(_ code: NSInteger, _ netData: Any?, _ error: Error?, _ url: URL?) {}
    
    ///继承这个这个类,并且 重写这个函数 来处理 成功code
    class func custom_handSucceedCodeFunc(_ netData: Any?, _ url: URL?) {}
    
    ///继承这个这个类,并且 重写这个函数 来处理 失败code
    class func custom_handDefeatCodeFunc(_ code: NSInteger,_ error: Error?, _ url: URL?) {}
    
    ///code log  处理
    class func handleCode (_ code: NSInteger, _ netData: Any?, _ error: Error?, _ url: URL?) -> (Bool) {
        custom_handCodeFunc(code, netData, error, url)
        if code / 100 == 2 {
            succeed(netData,url)
            custom_handSucceedCodeFunc(netData, url)
            return true
        }
        custom_handDefeatCodeFunc(code, error, url)
        defeat(code, error, url)
        return false
    }
}

/// log输出
private extension RespnseCodeMenager {
    
    class func succeed(_ netData: Any?, _ url: URL?) {
        if !isPrintSucceedNetWorkLog {
            return
        }
        
        let urlTemp: Any = url ?? "url 未知"
        let dataTemp: Any = netData ?? "data 未知"
        
        dPrint("\n\n✅✅✅请求成功\n✅\(urlTemp)\n")
        
        if let dataArray = (dataTemp as? Array<Any>) {
             dPrint("\netData(Array):--")
            for data in dataArray {
                dPrint(data)
            }
        }else{
            dPrint("\netData(Object):--")
            dPrint(dataTemp)
        }
        
        dPrint("✅✅✅\n\n\n\n")
    }
    
    class func defeat(_ code: NSInteger,_ error: Error?, _ url: URL?) {
        
        if !isPrintErrorNetWorkLog {
            return
        }
        
        let urlTemp: Any = url ?? "url 未知"
        let errorTemp: Any = error ?? "error 未知"
        
        dPrint("\n\n🌶🌶🌶请求失败\n\(code)\(urlTemp)\n")
        
        dPrint("\n🌶error:--")
        dPrint(errorTemp)
        dPrint("🌶🌶🌶\n\n\n\n")
    }
}

AlamofireMenager.swift

使用注意请求类型的区分,方法名称一致

  1. 根据success回调传入的<T>的类型返回对应的值
  2. T 为 [BaseMappable],则返回数组,
  3. T 为BaseMappable,则返回对象,
  4. T 为Any,则返回Json字符串。

object

//MARK: - 下载数据 相关接口
    
    /// alamofire 数据请求 (数据为object)
    ///注意循环引用
    /// - Parameters:
    ///   - path: url path
    ///   - method: 请求方式
    ///   - parameters: 参数
    ///   - parametersType: 参数为 query 还是 body
    ///   - responseDateType: 网络数据类型
    ///   - success: 成功的回调
    ///   - failure: 失败的回调
    /// - Returns: Request
    @discardableResult
    func loadData<T:BaseMappable>(Path path: String, HTTPMethod method: RequestMethod? = .get,_ parameters: [String:Any]? = nil,_ parametersType: ParamaetersType? = nil,_ setRequest: ((_ request: DataRequest) -> Void)? = nil,Success success: @escaping (_ M:T,_ response:DataResponse<T>) -> Void, Failure failure:@escaping(_ errorMsg:String) -> Void) -> (DataRequest?) {
        
        return self.loadDataObject(Path: path, HTTPMethod: method, parameters, parametersType, setRequest, Success: success, Failure: failure)
    }

Json

 /// alamofire 数据请求 (数据Json)
    ///注意循环引用
    /// - Parameters:
    ///   - path: url path
    ///   - method: 请求方式
    ///   - parameters: 参数
    ///   - parametersType: 参数为 query 还是 body
    ///   - responseDateType: 网络数据类型
    ///   - success: 成功的回调
    ///   - failure: 失败的回调
    /// - Returns: Request
    @discardableResult
    func loadData(Path path: String, HTTPMethod method: RequestMethod? = .get,_ parameters: [String:Any]? = nil,_ parametersType: ParamaetersType? = nil,_ setRequest: ((_ request: DataRequest) -> Void)? = nil,Success success: @escaping (_ json: Any,_ response: DataResponse<Any>) -> Void, Failure failure:@escaping(_ errorMsg:String) -> Void) -> (DataRequest?) {
        
        return self.loadDataJson(Path: path, HTTPMethod: method, parameters, parametersType, setRequest, Success: success, Failure: failure)
    }

array

/// alamofire 数据请求 (数据数组)
    ///注意循环引用
    /// - Parameters:
    ///   - path: url path
    ///   - method: 请求方式
    ///   - pxarameters: 参数
    ///   - parametersType: 参数为 query 还是 body
    ///   - responseDateType: 网络数据类型
    ///   - success: 成功的回调
    ///   - failure: 失败的回调
    /// - Returns: Request
    @discardableResult
    func loadData<T:BaseMappable>(Path path: String, HTTPMethod method: RequestMethod? = .get,_ parameters: [String:Any]? = nil,_ parametersType: ParamaetersType? = nil,_ setRequest: ((_ request: DataRequest) -> Void)? = nil,Success success: @escaping ([T],_ response: DataResponse<[T]>) -> Void, Failure failure:@escaping(_ errorMsg:String) -> Void) -> (DataRequest?) {
        
       return self.loadDataArray(Path: path, HTTPMethod: method, parameters, parametersType, setRequest, Success: success, Failure: failure)
    }

上传

///上传
    ///
    /// - Parameters:
    ///   - urlStr: url
    ///   - method: 请求方法
    ///   - params: 请求参数(根据key来拼接(后续可能回接入用户名与token))
    ///   - data: 照片数据
    ///   - name: 需要与后台协商成统一字段
    ///   - fileNameArray: 文件名称,看后台有没有要求
    ///   - headers: header 可以没有
    ///   - mimeType: mimeType
    ///   - success: 成功
    ///   - failture: 失败 -- 如果没有数据,相当于失败。
    func upload(_ urlStr : String,_ method: HTTPMethod, _ params:[String:String],_ data: [Data],_ names:[String],_ fileNames:[String], _ headers: [String:String]?,_ mimeType: String, success : @escaping (_ response : [String : AnyObject])->(), failture : @escaping (_ error : Error)->()) {
        
        self.uploadFunc(urlStr, method, params, data, names, fileNames, headers, mimeType, success: success, failture: failture)
    }

上传图片

///图片 上传
    ///
    /// - Parameters:
    ///   - urlStr: url
    ///   - method: 请求方法
    ///   - params: 请求参数(根据key来拼接(后续可能回接入用户名与token))
    ///   - data: 照片数据
    ///   - name: 需要与后台协商成统一字段
    ///   - fileNameArray: 文件名称,看后台有没有要求
    ///   - headers: header 可以没有
    ///   - mimeType: mimeType
    ///   - success: 成功
    ///   - failture: 失败 -- 如果没有数据,相当于失败。
    func uploadImage(_ urlStr : String,_ method: HTTPMethod, _ params:[String:String],_ images: [UIImage],_ names:String,_ fileNames:[String], _ headers: [String:String]?,_ compressionQuality: CGFloat? = 0.1 , _ mimeType: String ,success : @escaping (_ response : [String : AnyObject])->(), failture : @escaping (_ error : Error)->()) {
        self.uploadImageFunc(urlStr, method, params, images, names, fileNames, headers, compressionQuality, mimeType, success: success, failture: failture)
    }

图片:


请求错误

请求成功

测试必备搭配组件

  1. 在开发中,经常要切换环境,来测试各个环境下的代码。
  2. 总是改baseURL,然后运行,那不爽爆?写swift的小伙伴都懂
  3. 写一个后门儿,只有在release下才会显示出来,并且可以选择相应的url。
  4. 提供输入账号密码输入textField,点击登录自动切换账号。

可以在debug模式下,可以选择测试url
demo点这里

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

推荐阅读更多精彩内容