Вопрос: Архитектура клиентского приложения
Реализую проект клиентского приложения и возникла заморочка с архитектурой.
До этого были простые 3-4 оконные приложения. Впервые дали проект сложнее
Пытаюсь построить такую логику:
Общение приложения с сервером происходит на основе сервиса реализующего сокетное взаимодействия (NSStream) этот сервис пишет различные данные в локальную базу (Core data). И вот задача, при выводе определенной экранной формы на нее вывелись данные из БД, но если пришли новые данные, то надо передать их в этот контроллер для их вывода, либо перезапросить данные из БД, куда они были записаны.
Заморочка в том как дать понять контроллеру что пришедшие данные относятся именно к нему и отдать ему их если он представлен пользователю?
И стоит ли создавать сингтон сервиса, либо иницировать его в каждой экраной форме и закрывать при переходах, чтобы задавать ему делегат потоков (NSStream) и отрабатывать там данные
Думаю банальные вопросы, но не нашел на них ответа, а строить плохую архитектуру с самого начала прям не тянет
Ответ:
приведу пример того, как подобное решаю я
есть сервисы для коммуникации с API с соответствующими для этого методами. в каждом из методов создается модель request'a к API, которая загоняется в некий "RequestExecutor", обязанностью которого является дернуть данные из кеша(если таковые имеются), отдать их в "completion", а потом выполнить переданный ему запрос и по факту выполнения - снова передать эти данные в наш "completion".
итого имеем примерно такую ситуацию: заходим на какой-либо скрин - дергаем сервис(в моем случае архитектура VIPER, но детали реализации всего этого нам, в данном, случае неинтересны) - сервис выдает нам данные из кеша - мы отображаем их на вьюхе - сервис, тем временем, делает запрос уже к API - API отдает данные сервису, а сервис опять их отдает нам - мы обновляем данные на вьюхе.
надеюсь, доступно, теперь с примерами кода:
собссна, сам сервис:
Objective-C |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| final class AlertsService: BaseService, AlertsServiceType {
* * *
func fetchUserAlerts(page: Int, _ completion: @escaping (Result<[Alert]>) -> ()) {
let fetchAlertsRequest = FetchUserAlertsRequest()
fetchAlertsRequest.page = page
alertsRequestExecutor.execute(request: fetchAlertsRequest) { (result) in
completion(result)
}
}
* * *
} |
|
Этот самый "RequestExecutor":
Objective-C |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| class IncomingCacheRequestExecutor <ResponseType, ResultType>: IncomingCacheRequestExecutorType <ResponseType, ResultType> where ResponseType: Cacheable {
override func execute(request: BaseAPIRequest, _ completion: @escaping (Result<ResultType>) -> ()) {
if let cachedResponse = cache?.firstIncoming(ofType: ResponseType.self, typeID: ResponseType.typeIdentifier) {
DispatchQueue.main.async {
self.processResponse(cachedResponse, error: nil, isFromCache: true, completion: completion)
}
}
Alamofire.request(request.asURLRequest()).responseJsonObjectWIthMapping { [weak self] (response: ResponseType?, error) in
if let response = response {
self?.cache?.cacheIncoming(response)
}
self?.processResponse(response, error: error, isFromCache: false, completion: completion)
}
}
}
private extension IncomingCacheRequestExecutor {
private func processResponse(_ response: ResponseType?, error: Error?, isFromCache: Bool, completion: @escaping(Result<ResultType>) -> ()) {
guard let response = response else {
errorHandler?.handle(error: error, completion: completion)
return
}
responseProcessor.process(isCache: isFromCache, response: response, completion: completion)
}
} |
|
и метод ResponseProcessor'a для ясности:
Objective-C |
1
2
3
4
5
6
7
8
9
10
11
12
| override func process(isCache: Bool, response: AlertsResponse?, completion: @escaping (Result<[Alert]>) -> ()) {
guard isCache else {
completion(.success(alerts))
return
}
applyCachedCommands(to: alerts) { (processedAlerts) in
DispatchQueue.main.async {
completion(.success(processedAlerts))
}
}
} |
|
в ResponseProcessor'e идет обработка данных в зависимости от их источника. Вам, возможно, это не нужно и можно все упростить. в моем случае делался полноценный "offline-mode" с накаткой закешированных команд, с возможностью работать с фидом без подключения к инету, то есть удалять / добавлять и т.д.
ну и пользоваться этим вот так(как я говорил, у меня это интерактор, у вас что-то другое, возможно):
Objective-C |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func fetchAlerts(for page: Int, type: AlertsFetchType) {
let isFirstLoad = (page == 0)
alertsService.fetchUserAlerts(page: page) { [weak self] (result) in
guard let alerts = result.value else {
self?.output?.didFetchedAlerts(with: .failure(result.error ?? APIError.unknown), with: type)
return
}
if isFirstLoad {
self?.output?.didFetchedAlerts(with: .success(alerts), with: type)
} else {
self?.output?.didFetchedMoreAlerts(with: .success(alerts))
}
}
} |
|