Skip to content

Latest commit

 

History

History
128 lines (106 loc) · 4.17 KB

MultiTarget.md

File metadata and controls

128 lines (106 loc) · 4.17 KB

Advanced usage - use MultiTarget for multiple targets using the same Provider.

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.

Multiple targets when using associatedtype

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 associatedtypes.

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 `user` is implicitly `SessionModel` here
    }
}