Как на прошлом проекте происходила передача данных с сети на UI?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура передачи данных от сети к UI на проекте
На последнем проекте мы использовали многоуровневую архитектуру (Layered Architecture) с четким разделением ответственности. Основной подход — реактивное программирование с Combine для обеспечения однонаправленного потока данных и автоматического обновления UI.
Ключевые компоненты архитектуры:
- Сетевой слой (Network Layer)
- Слой репозиториев (Repository Layer)
- Слой бизнес-логики (Business Logic Layer)
- Презентационный слой (Presentation Layer)
Детальный процесс передачи данных:
1. Сетевой слой и сериализация
protocol NetworkServiceProtocol {
func fetch<T: Decodable>(_ endpoint: Endpoint) -> AnyPublisher<T, NetworkError>
}
class NetworkService: NetworkServiceProtocol {
private let session: URLSession
private let decoder: JSONDecoder
func fetch<T: Decodable>(_ endpoint: Endpoint) -> AnyPublisher<T, NetworkError> {
return session.dataTaskPublisher(for: endpoint.urlRequest)
.mapError { NetworkError.transportError($0) }
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard 200...299 ~= httpResponse.statusCode else {
throw NetworkError.serverError(statusCode: httpResponse.statusCode)
}
return data
}
.decode(type: T.self, decoder: decoder)
.mapError { error in
if let decodingError = error as? DecodingError {
return NetworkError.decodingError(decodingError)
}
return NetworkError.unknown(error)
}
.eraseToAnyPublisher()
}
}
2. Репозитории как абстракция над источниками данных
protocol UserRepositoryProtocol {
func fetchUsers() -> AnyPublisher<[User], Error>
func fetchUserDetails(id: String) -> AnyPublisher<UserDetail, Error>
}
class UserRepository: UserRepositoryProtocol {
private let networkService: NetworkServiceProtocol
private let cache: UserCacheProtocol
func fetchUsers() -> AnyPublisher<[User], Error> {
// Проверяем кэш, затем сеть
return cache.getUsers()
.catch { _ in
self.networkService.fetch(UsersEndpoint())
.handleEvents(receiveOutput: { [weak self] users in
self?.cache.saveUsers(users)
})
}
.eraseToAnyPublisher()
}
}
3. ViewModel с Combine для управления состоянием
class UserListViewModel: ObservableObject {
@Published private(set) var users: [User] = []
@Published private(set) var isLoading = false
@Published private(set) var error: Error?
private let repository: UserRepositoryProtocol
private var cancellables = Set<AnyCancellable>()
init(repository: UserRepositoryProtocol) {
self.repository = repository
}
func loadUsers() {
isLoading = true
error = nil
repository.fetchUsers()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.error = error
}
},
receiveValue: { [weak self] users in
self?.users = users
}
)
.store(in: &cancellables)
}
}
4. SwiftUI View с реактивными биндингами
struct UserListView: View {
@StateObject private var viewModel: UserListViewModel
var body: some View {
NavigationView {
ZStack {
List(viewModel.users) { user in
UserRow(user: user)
}
.navigationTitle("Users")
.refreshable {
viewModel.loadUsers()
}
if viewModel.isLoading {
ProgressView()
.scaleEffect(1.5)
}
if let error = viewModel.error {
ErrorView(error: error, retryAction: {
viewModel.loadUsers()
})
}
}
}
.onAppear {
if viewModel.users.isEmpty {
viewModel.loadUsers()
}
}
}
}
Ключевые принципы реализации:
Разделение ответственности: Каждый слой выполняет строго определенные задачи:
- Сетевой слой: HTTP-запросы и базовая обработка ошибок
- Репозитории: абстракция над источниками данных (сеть, кэш, база данных)
- ViewModel: подготовка данных для UI, бизнес-логика
- View: отображение данных и обработка пользовательских действий
Реактивность: Использование Combine обеспечивает:
- Автоматическое обновление UI при изменении данных
- Отмену асинхронных операций при деинициализации
- Композицию и трансформацию потоков данных
Обработка ошибок: Многоуровневая обработка:
- Сетевые ошибки (таймауты, статус коды)
- Ошибки парсинга JSON
- Ошибки бизнес-логики
- Пользовательский интерфейс для отображения ошибок
Кэширование: Стратегия "кэш-потом-сеть" (Cache-Then-Network):
- Быстрое отображение данных из кэша
- Фоновая загрузка актуальных данных
- Бесшовное обновление UI
Тестируемость: Каждый слой изолирован и тестируется отдельно:
- Моки для сетевого слоя
- Юнит-тесты для ViewModel
- UI-тесты для SwiftUI компонентов
Такой подход обеспечил стабильность, масштабируемость и поддерживаемость кода, позволяя легко добавлять новые фичи и модифицировать существующие без нарушения работы всей системы.