美文网首页
一个轻量化的网络请求

一个轻量化的网络请求

作者: seasonZhu | 来源:发表于2019-09-26 22:49 被阅读0次

    随着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)")
       }
    }
    

    相关文章

      网友评论

          本文标题:一个轻量化的网络请求

          本文链接:https://www.haomeiwen.com/subject/lwxoectx.html