Так, последние два дня пытался сделать нормальный Нетворк лайнер, чтобы можно было реюзить вовсе своих проектах, и впервые работал с дженериками в таком виде!!!
Поэтому если что-то можно подсократить, то проще подскажите!!!
Все началось с протокола 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?
}