Say, our application uses Moya for network request and ObjectMapper for JSON mapping. This could lead to duplicated or complicated code.
(yes, I think Alamofire is already complex enough, and Moya is a wrapper on top of it).
The approach
My approach to solve this is that, we could declare a shared NetworkRequestprotocol that also have associatetype like Moya'sTarget and ObjectMapper'sModel.
I think associatedtype like placeholders, or template in other languages.
Protocol with associated types
For for usage example, we have two Google network managers, defined in GoogleManager class cluster:
GoogleNetworkManager to get direction.
GooglePlaceNetworkManger to search for hotel.
both are from Google API.
We could conform them to NetworkRequest protocol, so they could have all traits we defined in the extensions.
This leads to better code, in my opinion.
Key things to remember π
The conformer just need to fill in associatedtype requirements we define in the conforming protocol.
The rest is automatically handled, since we write default protocol implementation in extension, so all conformers get those implementation for π (who doesn't want free stuffs π ?!)
For our case, basically NetworkRequest conformers all inherit this request method:
π NetworkRequest protocol with associatedtypes and extend/default implementation
import Foundation
import Moya
import ObjectMapper
import Moya_ObjectMapper
// Conformnace `Model` need to conform to MappableResponse that has these shared properties
protocol MappableResponse: Mappable {
var valid: Bool { get }
var errorMessage: String { get }
}
// Conformnace `Target` need to conform to TargetPlaceholder to provide generic enum to NetworkRequest
// request function
protocol TargetPlaceholder: TargetType {}
// Base network request
// Usage: conformer need to provide two informations.
// `Model`: model that request need to parse to
// `Target`: an TargetPlaceholder request route enum
protocol NetworkRequest {
typealias ModelCompletion = (NVResult<Model>) -> Void
associatedtype Target: TargetPlaceholder
associatedtype Model: MappableResponse
}
extension NetworkRequest {
// MARK: - Default implementation
func request(from provider: MoyaProvider<Target>, target: Target, for type: Model.Type, completion: @escaping ModelCompletion) {
log.debug(target.parameters)
provider.request(target) { result in
switch result {
case .success(let response):
do {
let mappedObject = try response.mapObject(type)
log.debug(mappedObject)
DispatchQueue.main.async {
// IMPORTANT: Should check if response is valid before dispatch completion result
if mappedObject.valid {
completion(NVResult.success(mappedObject))
} else {
errorResultHandler(message: mappedObject.errorMessage, completion: completion)
}
}
} catch let error {
errorResultHandler(message: error.localizedDescription, completion: completion)
}
case .failure(let error):
errorResultHandler(message: error.localizedDescription, completion: completion)
}
}
}
}
extension NetworkRequest {
// MARK: - Private
private func errorResultHandler(message: String, completion: @escaping ModelCompletion) {
log.error(message)
DispatchQueue.main.async {
let resultError = NVGeneralError.message(message)
completion(NVResult.failure(resultError))
}
}
}
π GoogleManager class cluster
import Foundation
class GoogleNetworkManager: NetworkRequest {
typealias GoogleDirectionParam = [String: String]
typealias ModelResponseCompletion = (NVResult<Model>) -> Void
// required: this is to fill placeholder requirements from NetworkRequest
typealias Model = GoogleResponse
typealias Target = GoogleTarget
// MARK: - Public
func direction(params: GoogleDirectionParam, completion: @escaping ModelResponseCompletion) {
request(from: GoogleProvider, target: Target.direction(params: params), for: Model.self, completion: completion)
}
}
class GooglePlaceNetworkManager: NetworkRequest {
typealias ModelResponseCompletion = (NVResult<Model>) -> Void
// required: this is to fill placeholder requirements from NetworkRequest
typealias Model = GooglePlaceResponse
typealias Target = GoogleTarget
// MARK: - Public
func searchForhotelWithName(_ query: String, completion: @escaping ModelResponseCompletion) {
guard query.isEmpty == false else { return }
request(from: GoogleProvider, target: Target.hotelSearch(query: query), for: Model.self, completion: completion)
}
}
Notes:
GoogleProvider is an enum that conform to Moya's TargetType protocol requirements.
GoogleResponse and GooglePlaceResponse are ObjectMapper models.
This apply to just Google network manager class, but in practice, we could just extend the same to all other new network classes.
I hope this useful (at least to my future self)~~!
The problem
Say, our application uses Moya for network request and ObjectMapper for JSON mapping. This could lead to duplicated or complicated code.
(yes, I think Alamofire is already complex enough, and Moya is a wrapper on top of it).
The approach
My approach to solve this is that, we could declare a shared
NetworkRequest
protocol that also have associatetype like Moya'sTarget
and ObjectMapper'sModel
.I think associatedtype like placeholders, or template in other languages.
Protocol with associated types
For for usage example, we have two Google network managers, defined in
GoogleManager
class cluster:GoogleNetworkManager
to get direction.GooglePlaceNetworkManger
to search for hotel.both are from Google API.
We could conform them to
NetworkRequest
protocol, so they could have all traits we defined in the extensions.This leads to better code, in my opinion.
Key things to remember π
The conformer just need to fill in associatedtype requirements we define in the conforming protocol.
The rest is automatically handled, since we write default protocol implementation in extension, so all conformers get those implementation for π (who doesn't want free stuffs π ?!)
For our case, basically
NetworkRequest
conformers all inherit this request method:Code sample
π
NetworkRequest
protocol with associatedtypes and extend/default implementationπ
GoogleManager
class clusterNotes:
This apply to just Google network manager class, but in practice, we could just extend the same to all other new network classes.
I hope this useful (at least to my future self)~~!
π