ajijoyo
1/4/2019 - 7:29 AM

Layer Network common used

Layer Network common used

//
//  NetworkProtocol.swift
//
//  Created for test in 2019
//  Created by Ajiejoy on 03/01/19 with love and sweat
//
//  Reach me on self.ajiejoy@gmail.com
//  Copyright © 2019 Ajiejoy. All rights reserved.
//


import UIKit

protocol NetworkProtocol : class {
    
    var log : Network.Log {get set}
    
    @discardableResult func request(method:Network.Method ,complete:((Network.Handler) -> Void)?) -> URLSessionDataTask?
    
    @discardableResult func request(method:Network.Method ,header:[[String:String]],complete:((Network.Handler) -> Void)?) -> URLSessionDataTask?
    
    @discardableResult func request(url urlRequest:URLRequest? , complete:((Network.Handler) -> Void)?) -> URLSessionDataTask?
}

struct Network {
    
    
    struct Error {
        var code : Int
        var description : String
    }
    
    enum Handler {
        case success(data:Any?)
        case failed(data:Any?,error:Error)
    }
    
    enum Method {
        
        case get(url:String)
        case post(url:String,params:Any?)
        case put(url:String,params:Any?)
        case delete(url:String,params:Any?)
        case multipart(url:String,params:Any?,parts:[Part]?)
        
        var rawValue: String {
            switch self {
            case .get:
                return "GET"
            case .post:
                return "POST"
            case .put:
                return "PUT"
            case .delete:
                return "DELETE"
            case .multipart:
                return "POST"
            }
        }
        
    }
    
    struct Part {
        var key : String
        var name : String
        var data : Data?
        var mimeType : String
        
        init(key:String,data:Data?,name:String = "\(NSUUID().uuidString).jpg",mimeType:String = "image/jpeg" ) {
            self.key = key
            self.data = data
            self.name = name
            self.mimeType = mimeType
        }
    }
    
    enum Log {
        case none
        case verbose
        case error
    }
}



class API : NetworkProtocol {
    
    var log : Network.Log = .verbose
    
    static var shared : NetworkProtocol = API()
    
    private static var defaultHeader : [[String:String]] {
        return [
            ["Content-Type":"application/json"],
            ["Accept":"application/json"]
        ]
    }
    
    private(set) var session : URLSession
    
    init() {
        let config = URLSessionConfiguration.default
        config.httpMaximumConnectionsPerHost = 2;
        config.timeoutIntervalForRequest = 30;
        config.requestCachePolicy = .reloadIgnoringCacheData
        config.networkServiceType = .default
        config.allowsCellularAccess = true
        session = URLSession(configuration: config)
        session.delegateQueue.qualityOfService = .userInitiated
    }
    
    @discardableResult func request(url urlRequest: URLRequest?, complete: ((Network.Handler) -> Void)?) -> URLSessionDataTask? {
        
        guard let srequest = urlRequest else {
            let er = Network.Error(code: 999, description: "Request Failed")
            complete?(.failed(data: nil, error: er))
            return nil
        }
        
        let task = helperRequest(withRequest: srequest, complete: complete)
        task.resume()
        
        return task
    }
    
    @discardableResult func request(method: Network.Method, complete: ((Network.Handler) -> Void)?) -> URLSessionDataTask? {
        return request(method: method, header: [], complete: complete)
    }
    
    @discardableResult func request(method: Network.Method, header: [[String : String]], complete: ((Network.Handler) -> Void)?) -> URLSessionDataTask? {
        
        var request : URLRequest?
        switch method {
        case .get(let url):
            guard let encod = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
                let er = Network.Error(code: 999, description: "Encoding URL Failed")
                complete?(.failed(data: nil, error: er))
                return nil
            }
            request = helperURL(string: encod, method: method.rawValue, header: header)
        case .post(let url, let params),.put(let url, let params),.delete(let url, let params):
            
            guard let encod = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
                let er = Network.Error(code: 999, description: "Encoding URL Failed")
                complete?(.failed(data: nil, error: er))
                return nil
            }
            
            if let params = params {
                do {
                    let data = try JSONSerialization.data(withJSONObject: params, options:.prettyPrinted)
                    let bodyString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
                    request = helperURL(string: encod, method: method.rawValue,body: bodyString?.data(using: String.Encoding.utf8.rawValue), header: header)
                } catch let error {
                    let er = Network.Error(code: 999, description: error.localizedDescription)
                    complete?(.failed(data: nil, error: er))
                }
            }
        case .multipart(let url, let params, let parts) :
            
            guard let encod = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
                let er = Network.Error(code: 999, description: "Encoding URL Failed")
                complete?(.failed(data: nil, error: er))
                return nil
            }
            
            let boundary = "----\(NSUUID().uuidString)"
            
            var body = Data()
            if let params = params as? Dictionary<String, Any> {
                for dic in params.enumerated(){
                    body.append("--\(boundary)\r\n".data(using: .utf8)!)
                    body.append("Content-Disposition: form-data; name=\"\(dic.element.key)\"\r\n\r\n".data(using: .utf8)!)
                    body.append("\(dic.element.value)\r\n".data(using: .utf8)!)
                }
            }
            
            if let parts = parts {
                for part in parts {
                    if let data = part.data {
                        body.append("--\(boundary)\r\n".data(using: .utf8)!)
                        body.append("Content-Disposition: form-data; name=\"\(part.key)\"; filename=\"\(part.name)\"\r\n".data(using: .utf8)!)
                        body.append("Content-Type: \(part.mimeType)\r\n\r\n".data(using: .utf8)!)
                        body.append(data)
                        body.append("\r\n".data(using: .utf8)!)
                    }
                }
            }
            body.append("--\(boundary)--\r\n".data(using: .utf8)!)
            request = helperURL(string: encod, method: method.rawValue,body: body, header: header)
        }
        
        guard let srequest = request else {
            let er = Network.Error(code: 999, description: "Request Failed")
            complete?(.failed(data: nil, error: er))
            return nil
        }
        
        let task = helperRequest(withRequest: srequest, complete: complete)
        task.resume()
        
        return task
    }
    
    private func helperURL(string:String,method:String,body:Data? = nil,header:[[String : String]]) -> URLRequest? {
        guard let url = URL(string: string) else { return nil }
        var request = URLRequest(url: url)
        request.addValue(String(body?.count ?? 0), forHTTPHeaderField: "Content-Length")
        let header = API.defaultHeader + header
        header.forEach { h in
            h.forEach({ (arg0) in
                let (key, value) = arg0
                request.setValue(value, forHTTPHeaderField: key)
            })
        }
        request.httpMethod = method
        request.httpBody = body
        
        return request
    }
    
    private func helperRequest(withRequest request:URLRequest,complete:((Network.Handler) -> Void)?) -> URLSessionDataTask {
        
        if log == .verbose {
            print("----------Start Request----------\n","URL :",request.url?.absoluteString ?? "nil","\nMethod :",request.httpMethod ?? "nil","\nHeader :",request.allHTTPHeaderFields ?? "nil","\n---------------------------------")
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            
            var code =  999
            var msg = error?.localizedDescription ?? "No Internet Connection"
            
            if let httpresponse = response as? HTTPURLResponse {
                code = httpresponse.statusCode
                
                if  let data = data,
                    let dict = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
                    let dictionary = dict as? [String:Any]{
                    
                    if 200 ... 299 ~= code {
                        
                        if self?.log == .verbose  {
                            print("-------------Response------------\n","URL :",httpresponse.url?.absoluteString ?? "nil","\nMethod :",request.httpMethod ?? "nil","\nHeader :",request.allHTTPHeaderFields ?? "nil"
                                ,"\nStatus :",code,"(\(HTTPURLResponse.localizedString(forStatusCode: code))","\nResult :",dictionary,
                                  "\n---------------------------------")
                        }
                        
                        complete?(.success(data: dictionary))
                        return
                    }
                    
                    if self?.log == .error || self?.log == .verbose {
                        print("-------------Response------------\n","URL :",httpresponse.url?.absoluteString ?? "nil","\nMethod :",request.httpMethod ?? "nil","\nHeader :",request.allHTTPHeaderFields ?? "nil","\nStatus :",code,"(\(HTTPURLResponse.localizedString(forStatusCode: code))","\nResult :",dictionary,
                              "\n---------------------------------")
                    }
                    
                    let er = Network.Error(code: code, description: msg)
                    complete?(.failed(data:dictionary,error:er))
                    return
                }
                
                msg = "JSONSerialization was failed"
                
                if self?.log == .error || self?.log == .verbose {
                    print("-------------Response------------\n","URL :",httpresponse.url?.absoluteString ?? "nil","\nMethod :",request.httpMethod ?? "nil","\nHeader :",request.allHTTPHeaderFields ?? "nil","\nStatus :",code,"\(msg)","\nResult : nil",
                          "\n---------------------------------")
                }
                
                let er = Network.Error(code: code, description: msg)
                complete?(.failed(data:nil,error:er))
                return
            }
            
            if self?.log == .error || self?.log == .verbose {
                print("-------------Response------------\n","URL :",request.url?.absoluteString ?? "nil","\nMethod :",request.httpMethod ?? "nil","\nHeader :",request.allHTTPHeaderFields ?? "nil","\nStatus :",code,"\(msg)","\nResult : nil",
                      "\n---------------------------------")
            }
            
            let er = Network.Error(code: code, description: msg)
            complete?(.failed(data:nil,error:er))
        }
        
        return task
    }
    
}


/* Example mocking
class MockCity : NetworkProtocol {
    
    
    
}
*/