Полезности, может кто подправит или даст критику!


#1

Так, последние два дня пытался сделать нормальный Нетворк лайнер, чтобы можно было реюзить вовсе своих проектах, и впервые работал с дженериками в таком виде!!!
Поэтому если что-то можно подсократить, то проще подскажите!!!

Все началось с протокола ApiRoutable

public protocol ApiRoutable: URLRequestConvertible {
    var baseURL: URLConvertible                 { get }
    var path: String                            { get }
    var method: HTTPMethod                      { get }
    var parameters: Encodable?                  { get }
    var headers: HTTPHeaders?                   { get }
    var urlParameters: URLParameterable?        { get }
}

extension ApiRoutable {
    var timeInterval: TimeInterval {
        return 60
    }
}

extension ApiRoutable {
    func asURLRequest() throws -> URLRequest {
        
        var baseUrl = try baseURL.asURL()
        baseUrl.appendPathComponent(path)
        
        var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)
        
        components?.query = urlParameters?.query
        
        guard let components_ = components else {throw NetworkError.invalidURLComponents(url: components?.string)}
        
        let request = try URLRequest(url: components_, method: method, headers: headers, parameters: parameters, timeInterval: timeInterval)
        
        return request
    }
}

Так как у нас на беке большенство параметров принимается в квери, то сделал под него отдельный протокол, которые наследуется от Encodable

public protocol URLParameterable: Encodable { }

    extension URLParameterable {
        var query: String {
            guard
                let dictionary = dictionary,
                !dictionary.isEmpty
                else {return String()}
            
            let queryText = dictionary
                .compactMap{"\($0)=\($1)"}
                .joined(separator: "&")
            
            return queryText
        }
    }

Который в свою Осередь расширен на методы

extension Encodable {
    var JSONData: Data? {
        return try? JSONEncoder().encode(self)
    }
    
    func toJSONData() throws -> Data {
        return try JSONEncoder().encode(self)
    }
}

extension Encodable {
    var dictionary: [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments))
            .flatMap{$0 as? [String: Any]}
    }
}

И базовый протокол непосредственно для запросов

protocol ApiServisable: class {
//    associatedtype Router: ApiRoutable
}

extension ApiServisable {
    
    func baseRequest<T: Decodable>(_ route: ApiRoutable, type: T.Type, completition: @escaping (Result<T>) -> ()) {
        
        if !(NetworkReachabilityManager()?.isReachable ?? false) {
            DispatchQueue.main.async {
                completition(.failure(NetworkError.badConnection))
            }
            return
        }
        
        do {
            let request = try route.asURLRequest()
            AF
                .request(request)
                .responseData { (response) in
                    switch response.result {
                    case .success(let data):
                        guard let status = response.response?.statusCode else {
                            DispatchQueue.main.async { completition(.failure(NetworkError.error(error: nil))) }
                            return
                        }
                        do {
                            let baseModel = try JSONDecoder().decode(T.self, from: data)
                            if (200...299).contains(status) {
                                DispatchQueue.main.async { completition(.success(baseModel)) }
                            } else if status == 401 {
                                throw NetworkError.notAuth
                            } else {
                                throw NetworkError.error(error: nil)
                            }
                        } catch {
                            DispatchQueue.main.async { completition(.failure(NetworkError.errorDecode(model: String(describing: T.self), error: error))) }
                        }
                    case .failure(let error):
                        if error._code == NSURLErrorTimedOut {
                            DispatchQueue.main.async { completition(.failure(NetworkError.timeOut(error: error))) }
                        } else {
                            DispatchQueue.main.async { completition(.failure(NetworkError.error(error: error))) }
                        }
                    }
            }
        } catch {
            DispatchQueue.main.async { completition(.failure(NetworkError.createRequestError(error: error))) }
        }
    }
}

А также расширение под rx

protocol ReactiveApiServicable: ReactiveCompatible, ApiServisable {
    associatedtype Router: ApiRoutable
    func request<T: Decodable>(_ route: Router, type: T.Type, completition: @escaping (Result<T>) -> ())
}

extension Reactive where Base: ReactiveApiServicable {
    func request<T: Decodable>(route: Base.Router, type: T.Type) -> Observable<Result<T>> {
        return Observable.create({ (observer) -> Disposable in
            self.base.request(route, type: type.self, completition: {
                (result) in
                observer.on(.next(result))
            })
            return Disposables.create()
        })
    }
}

extension ObservableType {
    func value<T>() -> Observable<T> where Self.Element == Result<T> {
        return filter{$0.value != nil}
            .map{$0.value!}
    }
    
    func error<T>() -> Observable<Error> where Self.Element == Result<T> {
        return filter{$0.error != nil}
            .map{$0.error!}
    }
}

Использование

енум с вашими эндпоинтами

enum ApiRouter {
    case sigin(queryCredentials: AuthCredentials)
    case confirm(queryCredentials: ConfirmCredentials)
    case orders
    case count
    case lastSms
}

extension ApiRouter: ApiRoutable {
     Ваши эндпоинты
}

Сам Сервис

public class ApiService<Router: ApiRoutable>: ReactiveApiServicable {
    
    func request<T: Decodable>(_ route: Router, type: T.Type, completition: @escaping (Result<T>) -> ()) {
        
        baseRequest(route, type: Response<T>.self) { (result) in
            switch result {
            case .success(let response):
                guard let model = response.data else {
                    DispatchQueue.main.async { completition(.failure(NetworkError.emptyData)) }
                    return
                }
                DispatchQueue.main.async { completition(.success(model)) }
            case .failure(let error):
                DispatchQueue.main.async { completition(.failure(error)) }
            }
        }
    }
}

extension ReactiveApiServicable where Router == ApiRouter {
    
    var orders: Observable<Result<[OrderModel]>> {
        return self.rx.request(route: .orders, type: [OrderModel].self)
    }
    
    var lastSms: Observable<Result<LastSms>> {
        return self.rx.request(route: .lastSms, type: LastSms.self)
    }
    
}

Где Response -это так сказать наш базовый ответ от сервера

class Response<T: Decodable>: Responseble {
    var status: Int
    var data: T?
    var errorCode: String?
    var errorMessage: String?
    var infoMessage: String?
}

#2

Очень интересно :slight_smile: мне как Junior
Критики пока нет:) Одни положительные эмоции.