Swift 3.0封装 URLSession 的GET/SET方法代替 Alamofire

来源:互联网 发布:淘宝店铺设计公司 编辑:程序博客网 时间:2024/03/19 12:21

升级到 Swift3.0 之后,新版本的 Alamofire 只支持 iOS 9.0 以上的系统,如果要适配 iOS 8,需要自己封装 URLSession,下面是笔者的方案:
这里使用的是 Swift 自己的原生类型 URLSession,而不是NSURLSession。
Alamofire 4.0 中的request方法的参数列表如下:

public func request(    _ url: URLConvertible,    method: HTTPMethod = .get,    parameters: Parameters? = nil,    encoding: ParameterEncoding = URLEncoding.default,    headers: HTTPHeaders? = nil)    -> DataRequest

method参数的类型是HTTPMethod,这个是系统类型,可以从外部传值,默认值是get。在request方法的方法体中,调用SessionManager.default.request方法,接受了全部的外部参数,并返回一个组装好的 DataRequest对象:

public func request(    _ url: URLConvertible,    method: HTTPMethod = .get,    parameters: Parameters? = nil,    encoding: ParameterEncoding = URLEncoding.default,    headers: HTTPHeaders? = nil)    -> DataRequest{    return SessionManager.default.request(        url,        method: method,        parameters: parameters,        encoding: encoding,        headers: headers    )}

SessionManager.default.request方法的实现如下:

@discardableResult    open func request(        _ url: URLConvertible,        method: HTTPMethod = .get,        parameters: Parameters? = nil,        encoding: ParameterEncoding = URLEncoding.default,        headers: HTTPHeaders? = nil)        -> DataRequest    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)            return request(encodedURLRequest)        } catch {            return request(failedWith: error)        }    }

这个方法主要完成的工作是加工request:使用url、method和headers三个参数创建一个URLRequest对象,然后把参数parameters中保存的HTTP请求携带的参数按照encoding所指定的编码方式进行编码得到最终的URLRequest对象,只有这两步都顺利完成了编码才算成功。成功后调用另一个重载的request方法,这个方法接受request字面量,可以直接传入生成的URLRequest对象。失败的情况下调用的是另一个重载的request方法,接受一个Error类型,实际上所有失败的情况下都会调用这个request方法。
所有重载版本的request方法最后都会返回一个 DataRequest类型,这个DataRequest是Alamofire封装的request对象,绕的有点晕。如果你准备自己封装,需要创建一个URLRequest对象代替DataRequest,这里我用了SwiftyJSON库,用来序列化网络返回的结果:

func httpRequest(url:String,method:HTTPMethod,parameters:[String:Any]?,completion:@escaping (_ json:JSON?,_ error:Error?)-> Void) {    //HTTP头部需要传入的信息,如果没有可以省略    var head:[String:String]?    //生成session    let config = URLSessionConfiguration.default    let session = URLSession(configuration: config)    let trueURL = URL(url)!    //请求成功时需要调用的代码封装为一个嵌套的方法,以便复用    func success(json:JSON){        completion(json,nil)    }    //同理请求失败需要执行的代码    func fail(error:Error,json:JSON){            completion(json,error)    }    do {        //自己封装一个request        let request = try URLRequest(url: trueURL, method: method, headers: head)        //这里我没有设置参数,使用了默认的编码方式        let encodedURLRequest = try URLEncoding.default.encode(request, with: parameters)        //生成一个dataTask        let dataTask = session.dataTask(with: encodedURLRequest) { (data, response, error) in        //下面是回调部分,需要手动切换线程            DispatchQueue.main.async {               //处理回调            }          }        defer{            dataTask.resume()        }    }    catch {        print(error)    }}

Alamofire的调用是函数式的,使用Alamofire请求返回一个son格式的数据的时候使用的是 responseJSON 方法,原来的格式大致如下:

        Alamofire.request(URL,method: .get,parameters:parameters,encoding:URLEncoding.default,headers:head).validate().responseJSON { response in            switch response.result {            case .success:                //成功的操作                //调用completion(json,error)            case .failure(let error):                //失败的操作                        //调用completion(json,error)            }            }

responseJSON方法的回调是基于result的状态的,但是原生的URLResponse对象没有这个状态,所以你需要自己去判断成功与失败的状态:

func httpRequest(url:String,method:HTTPMethod,parameters:[String:Any]?,completion:@escaping (_ json:JSON?,_ error:Error?)-> Void) {    var head:[String:String]?    //自定义HTTPlet config = URLSessionConfiguration.default    let session = URLSession(configuration: config)    let trueURL = URL(string: baseURL + url)!    func success(json:JSON){        completion(json,nil)    }    func fail(error:Error,json:JSON){       //错误处理        completion(json,error)    }    do {        let request = try URLRequest(url: trueURL, method: method, headers: head)        let encodedURLRequest = try URLEncoding.default.encode(request, with: parameters)        let dataTask = session.dataTask(with: encodedURLRequest) { (data, response, error) in            DispatchQueue.main.async {            //下面的几种情况参照了responseJSON方法的实现                guard error == nil else {                    fail(error: error!, json:JSON(NSNull()))                    return                }                if let response = response as? HTTPURLResponse, [204, 205].contains(response.statusCode) {                    success(json: JSON(NSNull()))                    return                }                guard let validData = data, validData.count > 0 else {                    fail(error:AFError.responseSerializationFailed(reason: .inputDataNil),json: JSON(NSNull()))                    return                }                //使用了SwiftyJSON的构造器                let js = JSON(data: validData)                success(json: js)            }        }        defer{            dataTask.resume()        }    }    catch {        print(error)    }}

此时如果删掉

import Alamofire

会发现有几处报错的地方,这是因为我们仍旧在使用Alamofire中的代码,首先HTTPMethod这个枚举类型是定义在Alamofire中的,因为原生API中指定HTTP方法使用的是字符串格式,编译器不会帮你检查错误,你可以把HTTPMethod的定义拷贝出来:

public enum HTTPMethod: String {    case options = "OPTIONS"    case get     = "GET"    case head    = "HEAD"    case post    = "POST"    case put     = "PUT"    case patch   = "PATCH"    case delete  = "DELETE"    case trace   = "TRACE"    case connect = "CONNECT"}

然后我们创建的URLRequest使用的实际上是Alamofire扩展的URLRequest,你需要自己动手写一个扩展,实现一个相同的构造器:

extension URLRequest {    public init(url: URL, method: HTTPMethod, headers: [String: String]? = nil)  {        self.init(url: url)        httpMethod = method.rawValue        if let headers = headers {            for (headerField, headerValue) in headers {                setValue(headerValue, forHTTPHeaderField: headerField)            }        }    }}

下一个问题是,Alamofire封装了一套把参数写进HTTP请求的编码方法,也就是你调用的:

let encodedURLRequest = try URLEncoding.default.encode(request, with: parameters)

这个方法中非常多依赖的方法,罗列如下:

public typealias Parameters = [String: Any]public func escape(_ string: String) -> String {    let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4    let subDelimitersToEncode = "!$&'()*+,;="    var allowedCharacterSet = CharacterSet.urlQueryAllowed    allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")    return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string}public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {    var components: [(String, String)] = []    if let dictionary = value as? [String: Any] {        for (nestedKey, value) in dictionary {            components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)        }    } else if let array = value as? [Any] {        for value in array {            components += queryComponents(fromKey: "\(key)[]", value: value)        }    } else if let value = value as? NSNumber {        if value.isBool {            components.append((escape(key), escape((value.boolValue ? "1" : "0"))))        } else {            components.append((escape(key), escape("\(value)")))        }    } else if let bool = value as? Bool {        components.append((escape(key), escape((bool ? "1" : "0"))))    } else {        components.append((escape(key), escape("\(value)")))    }    return components} func query(_ parameters: [String: Any]) -> String {    var components: [(String, String)] = []    for key in parameters.keys.sorted(by: <) {        let value = parameters[key]!        components += queryComponents(fromKey: key, value: value)    }    return components.map { "\($0)=\($1)" }.joined(separator: "&")} func encodesParametersInURL(with method: HTTPMethod) -> Bool {    switch method {    case .get, .head, .delete:        return true    default:        return false    }}public func encode(_ urlRequest: URLRequest, with parameters: Parameters?) throws-> URLRequest {    var urlRequest = urlRequest    guard let parameters = parameters else { return urlRequest }    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {        guard let url = urlRequest.url else {            throw AFError.parameterEncodingFailed(reason: .missingURL)        }        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)            urlComponents.percentEncodedQuery = percentEncodedQuery            urlRequest.url = urlComponents.url        }    } else {        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")        }        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)    }    return urlRequest}extension NSNumber {    fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }}

这一系列方法抛出的错误也是Alamofire自己定义的,拷贝出来:

public enum AFError: Error {    public enum ParameterEncodingFailureReason {        case missingURL        case jsonEncodingFailed(error: Error)        case propertyListEncodingFailed(error: Error)    }    public enum MultipartEncodingFailureReason {        case bodyPartURLInvalid(url: URL)        case bodyPartFilenameInvalid(in: URL)        case bodyPartFileNotReachable(at: URL)        case bodyPartFileNotReachableWithError(atURL: URL, error: Error)        case bodyPartFileIsDirectory(at: URL)        case bodyPartFileSizeNotAvailable(at: URL)        case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)        case bodyPartInputStreamCreationFailed(for: URL)        case outputStreamCreationFailed(for: URL)        case outputStreamFileAlreadyExists(at: URL)        case outputStreamURLInvalid(url: URL)        case outputStreamWriteFailed(error: Error)        case inputStreamReadFailed(error: Error)    }    public enum ResponseValidationFailureReason {        case dataFileNil        case dataFileReadFailed(at: URL)        case missingContentType(acceptableContentTypes: [String])        case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)        case unacceptableStatusCode(code: Int)    }    public enum ResponseSerializationFailureReason {        case inputDataNil        case inputDataNilOrZeroLength        case inputFileNil        case inputFileReadFailed(at: URL)        case stringSerializationFailed(encoding: String.Encoding)        case jsonSerializationFailed(error: Error)        case propertyListSerializationFailed(error: Error)    }    case invalidURL(url: URL)    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)    case multipartEncodingFailed(reason: MultipartEncodingFailureReason)    case responseValidationFailed(reason: ResponseValidationFailureReason)    case responseSerializationFailed(reason: ResponseSerializationFailureReason)}

现在报错的代码部分修改为:

func httpRequest(url:String,method:HTTPMethod,parameters:[String:Any]?,completion:@escaping (_ json:JSON?,_ error:Error?)-> Void) {    //HTTP头部需要传入的信息,如果没有可以省略    var head:[String:String]?    //生成session    let config = URLSessionConfiguration.default    let session = URLSession(configuration: config)    let trueURL = URL(url)!    //请求成功时需要调用的代码封装为一个嵌套的方法,以便复用    func success(json:JSON){        completion(json,nil)    }    //同理请求失败需要执行的代码    func fail(error:Error,json:JSON){            completion(json,error)    }    do {        //自己封装一个request        let request = try URLRequest(url: trueURL, method: method, headers: head)        //这里我没有设置参数,使用了默认的编码方式        let encodedURLRequest = try encode(request, with: parameters)        //生成一个dataTask        let dataTask = session.dataTask(with: encodedURLRequest) { (data, response, error) in        //下面是回调部分,需要手动切换线程            DispatchQueue.main.async {               //处理回调            }          }        defer{            dataTask.resume()        }    }    catch {        print(error)    }}
2 0
原创粉丝点击