When you have many endpoints you may end up with really long provider and
multiple switches on hundreds of cases. You could split the logic into multiple
targets but you would have to use multiple providers as well. This may
complicate your app logic and if you want to use the same plugins/closures for
each of them, it would require some work to maintain it. Instead, we can
use MultiTarget
enum that's built-in and really easy to use.
First, we have to define provider that will take multiple targets:
let provider = MoyaProvider<MultiTarget>()
Then, when you want to start the request, you need to replace
provider.request(.zen) { result in
// do something with `result`
}
to
provider.request(MultiTarget(GitHub.zen)) { result in
// do something with `result`
}
and that's it! Really simple to introduce it in your app and if you have many
endpoints that you want to split - this is the perfect solution for you. If you
want to see this API in action, check out our
Demo project, which has 2
targets: one of them is Demo
, which uses the basic form of Moya, and the
second one is DemoMultiTarget
, which uses the modified version with usage of
MultiTarget
.
Using Moya enables you to statically verify the arguments when invoking a
network request. You might want to extend Moya's TargetType
to verify your
custom types. One use case is to have the request
method return deserialized
models which vary based on request, instead of MoyaResponse
. This can be
achieved by adding an associatedtype
to TargetType
protocol DecodableTargetType: Moya.TargetType {
associatedType ResultType: SomeJSONDecodableProtocolConformance
}
enum UserApi: DecodableTargetType {
case get(id: Int)
case update(id: Int, name: String)
...
var baseURL: URL { ... }
var path: String { switch self ... }
var method: Moya.Method { ... }
typealias ResultType = UserModel
}
Because of associatedtype
, MultiTarget
cannot be used with DecodableTargetType
.
Instead, we can use the MultiMoyaProvider
variant. It does not require a
generic argument. Thus, requests can be invoked with any instance that
conforms to TargetType
. Using MultiMoyaProvider
allows you to write
request wrappers which can make use of your associatedtype
s.
For example, we can build a requestDecoded
method that returns ResultType
instead of MoyaResponse
as
extension MultiMoyaProvider {
func requestDecoded<T: DecodableTargetType>(_ target: T, completion: @escaping (_ result: Result<[T.ResultType], Moya.Error>) -> ()) -> Cancellable {
return request(target) { result in
switch result {
case .success(let response):
if let parsed = T.ResultType.parse(try! response.mapJSON()) {
completion(.success(parsed))
} else {
completion(.failure(.jsonMapping(response)))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
The beauty of this is that the type of input in the callback is implicitly determined from the target passed.
You can pass any DecodableTargetType
to start a request
let provider = MultiMoyaProvider()
provider.requestDecoded(UserApi.get(id: 1)) { result in
switch result {
case .success(let user):
// type of `user` is implicitly `UserModel`. Using any other type results
// in compile error
print(user.name)
}
}
When using associatedtype
, you will have to define different targets to work
with different types. For example, lets say we have another target SessionApi
struct SessionApi: DecodableTargetType {
typealias ResultType = SessionModel
}
which has a different ResultType
. We can use the same MultiMoyaProvider
instance
provider.requestDecoded(SessionApi.get) { result in
switch result {
case .success(let session):
// type of `session` is implicitly `SessionModel` here
}
}