Can i make a duplicate class as i can with functions - swift

So i have this code where i'm trying to make an task handler for requests. But in some cases the request doesn't get an model in response and therefor i don't want it to process any data. Hard to explain, but code shown below:
class UserTask<T: Codable>: ExecuteProtocol {
let userType: UserRequests
init(userType: UserRequests) {
self.userType = userType
}
var request: URLRequest {
return userType.build
}
public func run(completion: #escaping ((Response<T, NAError>) ->())) {
executeRequest(request: request) { (response) in
switch response {
case .success(let data):
completion(NADecoder<T>.decode(data: data).model)
break
case .failure(let error):
completion(.failure(error))
break
}
}
}
}
class UserTask: ExecuteProtocol {
let userType: UserRequests
init(userType: UserRequests) {
self.userType = userType
}
var request: URLRequest {
return userType.build
}
public func run(completion: #escaping ((Response<Any?, NAError>) ->())) {
executeRequest(request: request) { (response) in
switch response {
case .success(let data):
completion(.success(nil))
break
case .failure(let error):
completion(.failure(error))
break
}
}
}
}
This of course say Invalid redeclaration of 'UserTask' But can i do this in any smooth way? I have tried making the Codable optional and then unwrapping it. But as i want to keep the type of it in Decodable purpose it doesn't seem to work.
Any suggestions?

There is no need to create multiple classes for same functionality. You simply need to make some changes to a single class to support both your use-cases.
Instead of adding generic <T> to the class UserTask, add it to method run(completion:), i.e.
class UserTask: ExecuteProtocol {
let userType: UserRequests
init(userType: UserRequests) {
self.userType = userType
}
var request: URLRequest {
return userType.build
}
public func run<T: Codable>(type: T.Type, completion: #escaping ((Response<T?, NAError>) ->())) {
//your code here...
}
}
Call it like,
task.run(type: YourType.self) { (response) in
//add your code here...
}

Related

How does the `any Publisher` work in Swift?

I try to create a class implementation skeleton and a test for it. I have already had the function setResult and test testSetResult. Could I have something similar for the function setPublisher and testSetPublisher?
class FakeClass {
// ...
public func setResult() -> Result<[String: Any], Error> {
return .success([:])
}
public func setPublisher() -> any Publisher<[String, Any], Error> {
// what should I return? return PassthroughSubject()
}
}
class FakeClassTest: XCTestCase {
// ...
var fakeClass = DependencyInjectionFakeClass() // some dependency injection
func testSetResult() throws {
let result = fakeClass.setResult()
switch result {
case .success(let response):
XCTAsserture(response.isEmpty)
case .failure:
XCTFail()
}
// just a simple fake test
// How could I implement it or do we have sth similar with the function `testSetResult()`
func testSetPublisher() throws {
}
}

Cannot convert value of type 'MyEnum<T.Type>' to expected argument type 'MyEnum<_>'

I have a network layer working with generics and I'm using protocols so I can test it later. I have followed this tutorial https://medium.com/thecocoapps/network-layer-in-swift-4-0-972bf2ea5033
This is my Mock for testing:
import Foundation
#testable import TraktTest
class MockUrlSessionProvider: ProviderProtocol {
enum Mode {
case success
case empty
case fail
}
private var mode: Mode
init(mode: Mode) {
self.mode = mode
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: #escaping (NetworkResponse<T>) -> Void) where T: Decodable {
switch mode {
case .success: completion(NetworkResponse.success(T))
case .empty: completion(.failure(.noData))
case .fail: completion(.failure(.unknown("Error")))
}
}
}
I'm getting the error: Cannot convert value of type 'NetworkResponse<T.Type>' to expected argument type 'NetworkResponse<_>' in this line: completion(NetworkResponse.success(T))
If I send this to my completion success it compile:
try? JSONDecoder().decode(T.self, from: data!)
(dummy data that I created using encode and my model), but crash when get to my model because is nil despite I had encoded using JSONEncoder() with a correct model.
I think it works, because is the same logic that I use in my class that implements ProviderProtocol in my app:
final class URLSessionProvider: ProviderProtocol {
private var session: URLSessionProtocol
init(session: URLSessionProtocol = URLSession.shared) {
self.session = session
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: #escaping (NetworkResponse<T>) -> Void) where T: Decodable {
let request = URLRequest(service: service)
session.dataTask(request: request) { [weak self] data, response, error in
let httpResponse = response as? HTTPURLResponse
self?.handleDataResponse(data: data, response: httpResponse, error: error, completion: completion)
}.resume()
}
private func handleDataResponse<T: Decodable>(data: Data?, response: HTTPURLResponse?, error: Error?, completion: (NetworkResponse<T>) -> Void) {
guard error == nil else { return completion(.failure(.unknown(error?.localizedDescription ?? "Error"))) }
guard let response = response else { return completion(.failure(.unknown("no_response".localized()))) }
switch response.statusCode {
case 200...299:
guard let data = data, let model = try? JSONDecoder().decode(T.self, from: data) else { return completion(.failure(.noData)) }
completion(.success(model))
default: completion(.failure(.unknown("no_response".localized())))
}
}
}
URLSessionProtocol is just a protocol which has a method dataTask same as the one in URLSession.shared (receive a URLRequest and returns Data, Response and Error in a completion).
My Network responses are a couple of enums:
enum NetworkResponse<T> {
case success(T)
case failure(NetworkError)
}
enum NetworkError {
case unknown(String)
case noData
}
My provider protocol just have a function to make the request using generics:
protocol ProviderProtocol {
func request<T>(type: T.Type, service: ServiceProtocol, completion: #escaping(NetworkResponse<T>) -> Void) where T: Decodable
}
I don't think I need to use ServiceProtocol in my test because is to setup the request with endpoint, headers, body, id, etc. But this is the protocol I created:
typealias Headers = [String: String]
typealias Parameters = [String: Any]
protocol ServiceProtocol {
func baseURL() -> URL
var path: String? { get }
var id: String? { get }
var method: HTTPMethod { get }
var task: Task { get }
var headers: Headers? { get }
var parametersEncoding: ParametersEncoding { get }
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}
enum Task {
case requestPlain
case requestParameters(Parameters)
}
enum ParametersEncoding {
case url
case json
}
In my app, I have a class that implement ProviderProtocol and use a URLSession.shared to make the dataTask when some viewModel call the request with the appropiate model.
I'm use to make test with protocols and a specific model, but with generics is showing me that error. How can I achieve to have a mock provider using generics so I can test any viewModel who make a call to network using different kinds of models (stubs).
The error occurs because NetworkResponse expects an instance of T, while the mock tries to provide the actual T.
So, you need to somehow provide an instance, however this cannot be generated by the mock as it doesn't have enough information about how to construct an instance.
I recommend injecting the success value from the outside, when creating the mock. You can do this either by making the mock class generic, or by making the Mode enum generic. Below is a sample implementation for the latter:
class MockUrlSessionProvider: ProviderProtocol {
// making the enum generic, to support injecting the success value
enum Mode<T> {
case success(T)
case empty
case fail
}
// need to have this as `Any` to cover all possible T generic arguments
private var mode: Any
// however the initializer can be very specific
init<T>(mode: Mode<T>) {
self.mode = mode
}
func request<T>(type: T.Type, service: ServiceProtocol, completion: #escaping (NetworkResponse<T>) -> Void) where T: Decodable {
// if the mock was not properly configured, do nothing
guard let mode = mode as? Mode<T> else { return }
// alternatively you force cast and have the unit test crash, this should help catching early configuration issues
// let mode = mode as! Mode<T>
switch mode {
case let .success(value): completion(NetworkResponse.success(value))
case .empty: completion(.failure(.noData))
case .fail: completion(.failure(.unknown("Error")))
}
}
}

Replay last request with RxSwift

I'm creating a networking layer where I inject API provider and call event to the initialize method.
class NetworkingLayer<T: Decodable, E: TargetType> {
var response: Driver<T>
init(provider: MoyaProvider<E>, request: Singal<E>) {
let requestState = request
.flatMapLatest({
provider.rx.request($0).map(Respose<T>.self)
.map { ReqestState.loaded($0) }
.asDriver(onErrorRecover: { error in
return Driver.just(.error(error))
})
.startWith(.loading)
})
.asDriver(onErrorRecover: { fatalError($0.localizedDescription) })
response = requestState
.map({ $0.data?.data })
.filterNil()
}
}
Using them in the following way:
class ViewModel {
let networking: DataNetworkingResponse<[TagItem], ScoutEnpoint>
init(provider: MoyaProvider<Endpoint>, event: Singal<[Int]>) {
let request = event
.map({ Endpoint.getNewItems(prevItemsIds: $0) })
self.networking = NetworkingLayer(provider: provider, request: request)
}
}
All working like charm. But now I have to implement refresh feature. Refresh my last request. I've added this let refresh: Signal<()> = Signal.empty() property to my network layer. But can't understand how to save my last request.
class NetworkingLayer<T: Decodable, E: TargetType> {
let refresh: BehaviorRelay<()> = BehaviorRelay.init(value: ())
...
}
How can I implement refreshing like this?
viewModel.networking.refresh.accept(())
Using a BehaviorRelay is a bad idea because it emits a value as soon as it's subscribed to. This means that your NetworkingLayer object would make a request as soon as it's constructed.
Better would be to pass another Signal into the init method:
init(provider: MoyaProvider<E>, request: Singal<E>, refresh: Signal<Void>)
I don't know Moya but here's how I would have written something like that using URLSession:
enum RequestState<T> {
case loaded(T)
case error(Error)
case loading
}
class NetworkingLayer<T: Decodable> {
let response: Driver<RequestState<T>>
init(request: Signal<URLRequest>, refresh: Signal<Void>, provider: URLSession = URLSession.shared, decoder: JSONDecoder = JSONDecoder()) {
response = Observable.merge(request.asObservable(), refresh.withLatestFrom(request).asObservable())
.flatMapLatest { request in
provider.rx.data(request: request)
.map { try decoder.decode(T.self, from: $0) }
.map { RequestState.loaded($0) }
.startWith(.loading)
.catchError { Observable<RequestState<T>>.just(.error($0)) }
}
.share(replay: 1)
.asDriver(onErrorRecover: { fatalError($0.localizedDescription) })
}
}
I threw the share(replay:) on there so that subsequent subscribers won't make separate network requests, and I made the decoder injectable so the caller can configure it how they want.

Swift - refactoring common code to a protocol

I have multiple classes that have code that calls a common network class to make a GET api call. Below is an example of one:
public typealias Api1Result = (Result<Api1Model>) -> Void
private var path = "the/path/api1"
public enum Api1ServiceError: String, Error {
case error = "Sorry, the api1 service returned something different than expected"
}
extension Api1Model {
public static func getApi1(networkClient: NetworkClient = networkClient, completion: #escaping Api1Result) {
networkClient.getPath(path) { result in
switch result {
case .success(let data):
do {
let api1Model = try JSONDecoder().decode(Api1Model.self, from: data)
completion(.success(api1Model))
} catch {
completion(.failure(Api1ServiceError.error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Here is the Result enum if interested:
public enum Result<Value> {
case success(Value)
case failure(Error)
}
There are several other model classes, and the only difference is the actual model class being decoded (Api1Model in this case), as well as the completion typealias (Api1Result). It does the exact same thing across several others, just makes the call to the networkClient.getPath() method, checks for success/failure, and calls the completion closure.
Curious if there are any protocol experts out there who could assist in simplifying this and refactoring so I don't have the same boiler-plate code across multiple classes?
Use a protocol extension (untested)
protocol ApiModel {
associatedtype ApiType : Decodable = Self
static var path : String { get }
static func getApi1(networkClient: NetworkClient, completion: #escaping (Result<ApiType>) -> Void)
}
extension ApiModel where Self : Decodable {
static func getApi1(networkClient: NetworkClient, completion: #escaping (Result<ApiType>) -> Void) {
networkClient.getPath(path) { result in
switch result {
case .success(let data):
do {
let api1Model = try JSONDecoder().decode(ApiType.self, from: data)
completion(.success(api1Model))
} catch {
completion(.failure(Api1ServiceError.error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Make all your classes conform to ApiModel and add the static path property. The type alias is going to be inferred.

Swift: Pass type from property to generic function

For my networking module, I have this protocol that I adopt for accessing different parts of the API:
protocol Router: URLRequestConvertible {
var baseUrl: URL { get }
var route: Route { get }
var method: HTTPMethod { get }
var headers: [String: String]? { get }
var encoding: ParameterEncoding? { get }
var responseResultType: Decodable.Type? { get }
}
I'm adopting this with enums that look like this:
enum TestRouter: Router {
case getTestData(byId: Int)
case updateTestData(byId: Int)
var route: Route {
switch self {
case .getTestData(let id): return Route(path: "/testData/\(id)")
case .updateTestData(let id): return Route(path: "/testDataOtherPath/\(id)")
}
}
var method: HTTPMethod {
switch self {
case .getTestData: return .get
case .updateTestData: return .put
}
}
var headers: [String : String]? {
return [:]
}
var encoding: ParameterEncoding? {
return URLEncoding.default
}
var responseResultType: Decodable.Type? {
switch self {
case .getTestData: return TestData.self
case .updateTestData: return ValidationResponse.self
}
}
}
I want to use Codable for decoding nested Api responses. Every response consists of a token and a result which content is depending on the request route.
For making the request I want to use the type specified in the responseResultType property in the enum above.
struct ApiResponse<Result: Decodable>: Decodable {
let token: String
let result: Result
}
extension Router {
func asURLRequest() throws -> URLRequest {
// Construct URL
var completeUrl = baseUrl.appendingPathComponent(route.path, isDirectory: false)
completeUrl = URL(string: completeUrl.absoluteString.removingPercentEncoding ?? "")!
// Create URL Request...
var urlRequest = URLRequest(url: completeUrl)
// ... with Method
urlRequest.httpMethod = method.rawValue
// Add headers
headers?.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.key) }
// Encode URL Request with the parameters
if encoding != nil {
return try encoding!.encode(urlRequest, with: route.parameters)
} else {
return urlRequest
}
}
func requestAndDecode(completion: #escaping (Result?) -> Void) {
NetworkAdapter.sessionManager.request(urlRequest).validate().responseData { response in
let responseObject = try? JSONDecoder().decode(ApiResponse<self.responseResultType!>, from: response.data!)
completion(responseObject.result)
}
}
}
But in my requestAndDecode method It throws an compiler error (Cannot invoke 'decode' with an argument list of type '(Any.Type, from: Data)'). I can't use ApiResponse<self.responseResultType!> like that.
I could make this function generic and call it like this:
TestRouter.getTestData(byId: 123).requestAndDecode(TestData.self, completion:)
but then I'd have to pass the response type everytime I want to use this endpoint.
What I want to achieve is that the extension function requestAndDecode takes it response type information from itself, the responseResultType property.
Is this possible?
Ignoring the actual error report you have a fundamental problem with requestAndDecode: it is a generic function whose type parameters are determined at the call site which is declared to return a value of type Result yet it attempts to return a value of type self.responseResultType whose value is an unknown type.
If Swift's type system supported this it would require runtime type checking, potential failure, and your code would have to handle that. E.g. you could pass TestData to requestAndDecode while responseResultType might be ValidationResponse...
Change the JSON call to:
JSONDecoder().decode(ApiResponse<Result>.self ...
and the types statically match (even though the actual type that Result is is unknown).
You need to rethink your design. HTH
Create a Generic function with Combine and AlomFire. You can use it for all method(get, post, put, delete)
func fatchData<T: Codable>(requestType: String, url: String, params: [String : Any]?, myType: T.Type, completion: #escaping (Result<T, Error>) -> Void) {
var method = HTTPMethod.get
switch requestType {
case "Get":
method = HTTPMethod.get
case "Post":
method = HTTPMethod.post
print("requestType \(requestType) \(method) ")
case "Put":
method = HTTPMethod.put
default:
method = HTTPMethod.delete
}
print("url \(url) \(method) \(AppConstant.headers) ")
task = AF.request(url, method: method, parameters: params, encoding: JSONEncoding.default, headers: AppConstant.headers)
.publishDecodable(type: myType.self)
.sink(receiveCompletion: { (completion) in
switch completion{
case .finished:
()
case .failure(let error):
// completion(.failure(error))
print("error \(error)")
}
}, receiveValue: {
[weak self ](response) in
print("response \(response)")
switch response.result{
case .success(let model):
completion(.success(model))
print("error success")
case .failure(let error):
completion(.failure(error))
print("error failure \(error.localizedDescription)")
}
}
)
}