Вот как выглядит SOA и MVPVM в моем последнем приложении (это экран который отображает магазины на google картах):
StoresTarget (Транспортный слой построен на Moya):
extension TargetType {
var baseURL: URL { return URL(string: "https://my-api-base-url")! }
var parameterEncoding: ParameterEncoding { return JSONEncoding() }
var task: Task { return .request }
}
enum StoresTarget {
case getAllStores
}
extension StoresTarget: TargetType {
var path: String { return "/mySecretPath" }
var method: Moya.Method { return .get }
var parameters: [String : Any]? { return nil }
}
StoresService:
class BasicService<Target: TargetType> {
let localAuthenticationService = LocalAuthenticationService()
var provider: RxMoyaProvider<Target> {
return
RxMoyaProvider<Target>(
endpointClosure: { [unowned self] target in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
if let sessionIDCookie = self.localAuthenticationService.sessionIDCookie {
return defaultEndpoint.adding(httpHeaderFields: [
"Cookie" : sessionIDCookie
])
return defaultEndpoint
}
)
}
let disposeBag = DisposeBag()
private typealias Seconds = Double
let defaultRequestTimeout = RxTimeInterval(Seconds(20))
}
class StoresService: BasicService<StoresTarget> {
let dao = try! ArrayStoresResponseDAO()
func getAllStores(_ strategy: RequestStrategy) -> Observable<StoresResponse> {
switch strategy {
case .api:
return getAllStoresFromDAO().concat(getAllStoresFromAPI())
case .cached:
return getAllStoresFromDAO()
}
}
private func getAllStoresFromAPI() -> Observable<StoresResponse> {
return
provider
.request(.getAllStores)
.observeOn(utilityScheduler)
.timeout(defaultRequestTimeout, scheduler: utilityScheduler)
.mapResponse(StoresResponse.self)
//Сохранение в json файл
.do(
onNext: { [unowned self] response in
try self.dao.persist(entity: response)
})
.observeOn(MainScheduler.instance)
}
private func getAllStoresFromDAO() -> Observable<StoresResponse> {
return
Observable
.just()
.observeOn(utilityScheduler)
.map { [unowned self] in
guard let response = self.dao.getAll().last else { throw NSError(domain: "DAO is empty?", code: 404, userInfo: nil) }
return response
}
.observeOn(MainScheduler.instance)
}
}
MapPresentationModel:
class MapPresentationModel: BasicPresentationModel {
private let service = StoresService()
func getAllStores() -> Observable<[MapStoreViewModel]> {
networkIsActive.on(.next(true))
return
service
.getAllStores(.api)
.observeOn(utilityScheduler)
.map { response in
response.storeList.map{ MapStoreViewModel($0) }
}
.observeOn(MainScheduler.instance)
.do(
onDisposed: { [unowned self] in self.networkIsActive.on(.next(false))}
)
}
}
MapViewController:
class MapViewController: UIViewController, RouterType {
let disposeBag = DisposeBag()
let presentationModel = MapPresentationModel()
var mapView: GMSMapView!
var router: AnyRouter! {
return MapRouter(viewController: self)
}
var mapStoreViewModels: [MapStoreViewModel] = []
override func viewDidLoad() {
super.viewDidLoad()
configureHUD()
configureMap()
}
func configureMap() {
let moscowCoordinates = CLLocationCoordinate2D(latitude: 55.7558, longitude: 37.6173)
mapView = GMSMapView.map(
withFrame: view.bounds,
camera: GMSCameraPosition.camera(withTarget: moscowCoordinates, zoom: 12)
)
mapView.delegate = self
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
view.addSubview(mapView)
presentationModel
.getAllStores()
.subscribe(
onNext: { [unowned self] viewModels in
for viewModel in viewModels {
let marker = StoreMarker(position: store.coordinate.toCLLocation().coordinate)
marker.title = viewModel.title
marker.store = viewModel.store
marker.map = self.mapView
}
self.mapStoreViewModels = viewModels
})
.addDisposableTo(disposeBag)
}
func configureHUD() {
PKHUD.sharedHUD.contentView = PKHUDSystemActivityIndicatorView()
presentationModel.networkIsActive
.subscribe(onNext: { active in
switch active {
case true:
PKHUD.sharedHUD.show()
case false:
PKHUD.sharedHUD.hide()
}
}).addDisposableTo(disposeBag)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if
let destination = segue.destination as? RouterType,
let mapStoreViewModel = sender as? MapStoreViewModel {
router
.pass(mapStoreViewModel.store, to: destination.router)
}
}
}