Объясните плиз)


#1

Добрый вечер, пытаюсь разобраться с дженериками и как-то начать их более менее юзать!

В общем, поставил себе боевую задачу, ниже класс сервиса для запросов, с одной функцией, у которой ы сигнатуре route - енум из параметров которого собирается request и completition, в общем тут все понятно

Необращайте внимание пока на обработку ошибок в ответе, это к месту пока не относится, допишу)))

public class ApiService<Router: ApiRoutable>: NetworkRouter {
    
    private var task: URLSessionTask?
    
    func request<T: Decodable>(_ route: Router, completition: @escaping (T?, Error?) -> ()) {
        let session = URLSession.shared
        
        do {
            let request = try route.asURLRequest()
            task = session.dataTask(with: request, completionHandler: {
                (data, response, error) in
                
                do {
                    let model = try JSONDecoder().decode(Response<T>.self, from: data!)
                    completition(model.data, nil)
                } catch {
                    RouterLogger.error("ApiService", "request", "\(String(describing: T.self)) Error decode", error: error)
                }
            })
        } catch {
            RouterLogger.error("ApiService", "request", "request", error: error)
        }
        
        task?.resume()
    }
    
    func cancel() {
        task?.cancel()
    }   
}

Дальше я хочу сделать реактивное расширение под запросы, Вопрос

можно ли это как-то сделать дженериком, долго мучайся, но скорее всего тупенький, и с расширением ничего особо не получилось, поэтому просто сделал ф-ю возвращающую обсервабл

extension ApiService {
    func getObserver(route: Router) -> Observable<[OrderModel]> {
        
        return Observable.create({
            (observer) -> Disposable in
            self.request(route, completition: {
                (a, error) in
                
                
                observer.onNext(a!)
            })
            return Disposables.create()
        })
        
    }
}

И так вопрос, в какой момент модель ответа a кастится к [OrderModel]
В момент когда я оннекстом посылаю Decodable?!

Это вообще жизнеспособный способ работы с сетью!?

И можно-ли как-то сделать универсальный метод, в который указывать тип, он будет возвращать нужную модель от декодубла!??!


#2

Сделаль)))

Завтра выложу в подвылизаном виде


#3

Используйте Result вместо двух Optional, он как раз для этого :slight_smile:

public class ApiService<Router: ApiRoutable>: NetworkRouter {
    enum Err: Error { case dataIsUndefined }
    private var task: URLSessionTask?
    
    func request<T: Decodable>(_ route: Router, completion: @escaping (Result<T, Error>) -> Void) {
        do {
            let request = try route.asURLRequest()
            task = URLSession.shared.dataTask(with: request) { data, _, error in
                do {
                    guard error == nil else { throw error! }
                    guard let data = data else { throw Err.dataIsUndefined }
                    completion(.success(try JSONDecoder().decode(Response<T>.self, from: data).data))
                } catch {
                    completion(.failure(error))
                }
            }
        } catch {
            completion(.failure(error))
        }
        task!.resume()
    }
    
    func cancel() {
        task?.cancel()
    }
}
extension ApiService {
    func getObserver<T: Decodable>(route: Router) -> Observable<T> {
        return Observable.create { observer in
            self.request(route) { (result: Result<T, Error>) in
                do {
                    observer.on(.next(try result.get()))
                    observer.on(.completed)
                } catch {
                    observer.on(.error(error))
                }
            }
            return Disposables.create()
        }
    }
}

#4

Да это понятно))

Суть не в этом была, а именно как дженерики сделать и передавать тип, оказалось не сложно, @haymob к вашему мнению прислушиваюсь, как вам такой подход?!

Для реактивны просто можно сделать расширение
extension Reactive where Base: ApiService<ApiRouter>

А вот основной

func request<T: Decodable>(_ route: Router, type: T.Type, completition: @escaping (Result<T>) -> ()) {
        let session = URLSession.shared
        
        do {
            let request = try route.asURLRequest()
            task = session.dataTask(with: request, completionHandler: {
                (data, response, error) in
                
                if let response = response as? HTTPURLResponse {
                    let result = self.handleNetworkResponse(response, data: data, error: error, type: T.self)
                    
                    DispatchQueue.main.async {
                        completition(result)
                    }
                    
                }
            })
        } catch {
            completition(.failure(error))
        }
        task?.resume()
    }

fileprivate func handleNetworkResponse<T: Decodable>(_ response: HTTPURLResponse, data: Data?, error: Error?, type: T.Type) -> Result<T>{
        
        if let error = error { return .failure(error) }
        
        switch response.statusCode {
        case 200...299:
            guard let data = data else {return .failure(NetworkError.emptyData)}
            do {
                let model = try JSONDecoder().decode(Response<T>.self, from: data)
                guard let response = model.data else {return .failure(NetworkError.emptyData)}
                return .success(response)
            } catch {
                return .failure(error)
            }
        case 401...500: return .failure(NetworkError.authenticationError)
        case 501...599: return .failure(NetworkError.badRequest)
        case 600: return .failure(NetworkError.outdated)
        default: return .failure(NetworkError.failed)
        }
    }


**enum** Result&lt;T&gt;{

**case** success(T)

**case** failure(Error)

}

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

a.request(.orders, type: [OrderModel].self) {
            (result) in
            switch result {
            case .success(let orders):
                print("DATA: ", orders)
            case .failure(let error):
                print("Error: ", error.localizedDescription)
            }
        }
        
        a.request(.count, type: Int.self) {
            (result) in
            switch result {
            case .success(let count):
                print(count)
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
        
        a.request(.confirm(queryCredentials: ConfirmCredentials(login: "Jack", code: "123")),
                  type: ConfirmModel.self) {
                    (result) in
                    switch result {
                    case .success(let confirm):
                        print(confirm.name)
                    case .failure(let error):
                        print(error.localizedDescription)
                    }
        }

#5

Type лучше не предавать в функцию, а указывать явно, как здесь:

self.request(route) { (result: Result<T, Error>) in

З.Ы. Хотя дело вкуса)


#6

ну да))

Фуууух, вроде избавился от кучи кода по парсингу)))