一个轻量化的网络请求

2019-09-26  本文已影响0人  seasonZhu

随着Alamofire5.0的发布,各位想要的抑或不想要的功能,各位会调用抑或不会调用的功能它都有了.
都有的同时,也使得它4.0的版本相比实在大了不少.
如果只是写个Demo,做个测试,这货实在是太大了,于是乎想着用分类写一个简单点的网络请求,只支持GET与POST

// MARK: - 封装网络请求方式
extension URLRequest {
    
    /// 直接从Alamofire5.0中拿的
    
    /// 请求方式
    public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
        /// `GET` method.
        public static let get = HTTPMethod(rawValue: "GET")
        /// `POST` method.
        public static let post = HTTPMethod(rawValue: "POST")
        
        public let rawValue: String

        public init(rawValue: String) {
            self.rawValue = rawValue
        }
    }
    
    /// 请求方式
    public var requestMethod: HTTPMethod? {
        get { return httpMethod.flatMap(HTTPMethod.init) }
        set { httpMethod = newValue?.rawValue }
    }

}

// MARK: - 封装网络请求中的错误
extension URLRequest {
    
    /// 详细错误
    ///
    /// - urlInitFailed: URL初始化失败,返回值为nil
    /// - notSupportRequestType: 不支持的请求方式(目前仅支持get与post)
    /// - urlComponentsInitFailed: URLComponents初始化失败,返回值为nil
    /// - getComponentsUrlFailed: 获取URLComponents对象的url失败,返回值为nil
    /// - addingPercentEncodingError: 字符串进行addingPercentEncoding编码方法失败,返回为nil
    /// - stringTransformDataError: 字符串通过utf8编码为Data类型失败,返回为nil
    /// - noData: 请求返回的数据为空
    /// - jsonDecodingError: 请求返回的数据反序列化失败
    /// - urlSessionDataTaskError: 请求时出现了错误
    public enum DetailError: Error {
        case urlInitFailed
        case notSupportRequestType
        case urlComponentsInitFailed
        case getComponentsUrlFailed
        case addingPercentEncodingError
        case stringTransformDataError
        case noData(response: URLResponse?)
        case jsonDecodingError(error: Error)
        case urlSessionDataTaskError(error: Error)
    }
}

extension URLRequest.DetailError: CustomStringConvertible {
    public var description: String {
        let message: String
        switch self {
        case .urlInitFailed:
            message = "URL初始化失败,返回值为nil"
        case .notSupportRequestType:
            message = "不支持的请求方式(目前仅支持get与post)"
        case .urlComponentsInitFailed:
            message = "URLComponents初始化失败,返回值为nil"
        case .getComponentsUrlFailed:
            message = "获取URLComponents对象的url失败,返回值为nil"
        case .addingPercentEncodingError:
            message = "字符串进行addingPercentEncoding编码方法失败,返回为nil"
        case .stringTransformDataError:
            message = "字符串通过utf8编码为Data类型失败,返回为nil"
        case .noData(_):
            message = "请求返回的数据为空"
        case .jsonDecodingError(_):
            message = "请求返回的数据反序列化失败"
        case .urlSessionDataTaskError(_):
            message = "请求时出现了错误"
        }
        return message
    }
}

// MARK: - 封装网络请求
extension URLRequest {
    
    static let defaultSession = URLSession(configuration: .default)
    
    static var task: URLSessionDataTask?
    
    /// GET请求
    ///
    /// - Parameters:
    ///   - baseUrl: 基本网址
    ///   - api: Api
    ///   - HTTPHeaders: 请求头
    ///   - params: 请求参数
    ///   - completionHandler: 完成回调
    public static func get<T: Codable>(baseUrl: String,
                                       api: ApiProtocol? = nil,
                                       HTTPHeaders: [String: String]? = nil,
                                       params: [String: String]? = nil,
                                       completionHandler: @escaping (Swift.Result<(T, HTTPURLResponse?), URLRequest.DetailError>) -> Void) {
        request(baseUrl: baseUrl, requestMethod: .get, api: api, HTTPHeaders: HTTPHeaders, completionHandler: completionHandler)
    }
    
    /// POST请求
    ///
    /// - Parameters:
    ///   - baseUrl: 基本网址
    ///   - api: Api
    ///   - HTTPHeaders: 请求头
    ///   - params: 请求参数
    ///   - completionHandler: 完成回调
    public static func post<T: Codable>(baseUrl: String,
                                        api: ApiProtocol? = nil,
                                        HTTPHeaders: [String: String]? = nil,
                                        params: [String: String]? = nil,
                                        completionHandler: @escaping (Swift.Result<(T, HTTPURLResponse?), URLRequest.DetailError>) -> Void) {
        request(baseUrl: baseUrl, requestMethod: .post, api: api, HTTPHeaders: HTTPHeaders, completionHandler: completionHandler)
    }
    
    /// 通用请求
    ///
    /// - Parameters:
    ///   - baseUrl: 基本网址
    ///   - requestMethod: 请求方式 (目前仅支持get与post)
    ///   - api: Api
    ///   - HTTPHeaders: 请求头
    ///   - params: 请求参数
    ///   - completionHandler: 完成回调 注意是一个Result类型 Success是(T, HTTPURLResponse?)的元组, Failure是URLRequest.DetailError 自定义的Error枚举
    private static func request<T: Codable>(baseUrl: String,
                                           requestMethod: URLRequest.HTTPMethod,
                                           api: ApiProtocol? = nil,
                                           HTTPHeaders: [String: String]? = nil,
                                           params: [String: String]? = nil,
                                           completionHandler: @escaping (Swift.Result<(T, HTTPURLResponse?), URLRequest.DetailError>) -> Void) {
        
        /// 网址字符串拼接
        let queryURLString = baseUrl + (api?.api ?? "")
        
        /// 网址URL生成
        guard let queryURL = URL(string: queryURLString) else {
            completionHandler(.failure(.urlInitFailed))
            return
        }
        
        switch requestMethod {
        case .get:
            
            /// get请求的参数序列化
            
            guard var components = URLComponents(url: queryURL, resolvingAgainstBaseURL: true) else {
                completionHandler(.failure(.urlComponentsInitFailed))
                return
            }
            
            if let params = params {
                // 对于一个字典进行enumerated(), 第一个其实元素所在的序列顺序的个数,而后面会将字典的[key: value]改为元组形式的(key: String, value: String)这样方便进行.出来
                for (_, tuple) in params.enumerated() {
                    components.queryItems?.append(URLQueryItem(name: tuple.key, value: tuple.value))
                }
            }
            
            guard let url = components.url else {
                completionHandler(.failure(.getComponentsUrlFailed))
                return
            }
            
            var request = URLRequest(url: url)
            request.httpMethod = requestMethod.rawValue
            if let headers = HTTPHeaders {
                for (_, tuple) in headers.enumerated() {
                    request.setValue(tuple.value, forHTTPHeaderField: tuple.key)
                }
            }
            
            urlSessionRequest(request, completionHandler: completionHandler)
            
        case .post:
            
            /// post请求的参数序列化
            
            var request = URLRequest(url: queryURL)
            request.httpMethod = requestMethod.rawValue
            
            if let headers = HTTPHeaders {
                for (_, tuple) in headers.enumerated() {
                    request.setValue(tuple.value, forHTTPHeaderField: tuple.key)
                }
            }
            
            if let params = params {
                let queryString = params.compactMap { "\($0) = \($1)" }.joined(separator: "&")
                
                let generalDelimitersToEncode = ":#[]@"
                let subDelimitersToEncode = "!$&'()*+,;="
                
                var allowedCharacterSet = CharacterSet.urlQueryAllowed
                allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
                
                guard let query = queryString.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else {
                    completionHandler(.failure(.addingPercentEncodingError))
                    return
                }
                
                guard let data = query.data(using: .utf8) else {
                    completionHandler(.failure(.stringTransformDataError))
                    return
                }
                request.httpBody = data
            }
            
            urlSessionRequest(request, completionHandler: completionHandler)
            
        default:
            completionHandler(.failure(.notSupportRequestType))
        }
        
    }
    
    /// URLSession单例的dataTask请求
    ///
    /// - Parameters:
    ///   - request: URLRequest
    ///   - completionHandler: 完成回调
    private static func urlSessionRequest<T: Codable>(_ request: URLRequest,
                                                      completionHandler: @escaping (Swift.Result<(T, HTTPURLResponse?), URLRequest.DetailError>) -> Void) {
        
        task?.cancel()
        
        task = defaultSession.dataTask(with: request) { (data, response, error) in
            defer {
                self.task = nil
            }
            
            guard let data = data else {
                DispatchQueue.main.async {
                    completionHandler(.failure(.noData(response: response)))
                }
                return
            }
            
            guard error == nil else {
                DispatchQueue.main.async {
                    completionHandler(.failure(.urlSessionDataTaskError(error: error!)))
                }
                return
            }
            
            do {
                let object = try JSONDecoder().decode(T.self, from: data)
                DispatchQueue.main.async {
                    completionHandler(.success((object, response as? HTTPURLResponse)))
                }
            } catch {
                DispatchQueue.main.async {
                    #if DEBUG
                    print("JSON Decoding Error: \(error)")
                    #endif
                    completionHandler(.failure(.jsonDecodingError(error: error)))
                }
            }
        }
        task?.resume()
    }
}

/// Api协议
public protocol ApiProtocol: CustomStringConvertible {
    var api: String { get }
}

extension ApiProtocol {
    public var api: String {
        return description
    }
}

使用的时候如下

/// 定义模型

struct TopicItem: Codable {
    let topicImageUrl: String?
    let topicOrder: Int?
    let id: Int?
    let upTime: String?
    let topicStatus: Int?
    let topicTittle: String?
    let topicDesc: String?
}

struct TopicModel: Codable {
    let code: Int?
    let list: [TopicItem]
}

请求

/// 注意返回的result类型,有点复杂,但是目前只能这样
URLRequest.post(baseUrl: "http://sun.topray-media.cn/tz_inf/api/topics") { (result: Swift.Result<(TopicModel, HTTPURLResponse?), URLRequest.DetailError>) in
  switch result {
  case .success(let model, let response):
    print("model:\(model)")
    print("status code: \(response?.statusCode)")
   case .failure(let error):
     print("error:\(error)")
   }
}
上一篇下一篇

猜你喜欢

热点阅读