Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
Tags
- Refactoring
- rxswift
- swiftUI
- 리펙터링
- map
- Xcode
- 리팩토링
- uitableview
- ribs
- 클린 코드
- 애니메이션
- clean architecture
- Clean Code
- Observable
- HIG
- UICollectionView
- 리펙토링
- RxCocoa
- combine
- SWIFT
- swift documentation
- 스위프트
- tableView
- Human interface guide
- ios
- Protocol
- collectionview
- MVVM
- UITextView
- uiscrollview
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS] 2. Clean Architecture + MVVM 개념 확실하게 이해하기 (의존 관계 Presentation, Domain, Data) 본문
Architecture (swift)/MVVM (개념)
[iOS] 2. Clean Architecture + MVVM 개념 확실하게 이해하기 (의존 관계 Presentation, Domain, Data)
jake-kim 2021. 8. 18. 00:26의존관계
- 잘 변하는 것에서 변하지 않는것으로 의존관계가 되는게 이상적인 형태
- 잘 변하지 않는 계층인 Domain계층으로 Presentation과 Data 계층이 의존하는 형태
- 핵심: Actor가 Entity를 확인하는 flow
- View는 ViewModel의 메소드를 호출
- viewModel은 useCase 실행 > useCase는 Repository(DB or Network)에 데이터 요청
- Repository에서 cache에 데이터가 있으면 바로 획득, 없으면 memory cache, disk cache로 기록
- Respository로 부터 받은 데이터는 completion의 인수로 받을수 있어서 ViewModel이 이 데이터 획득
- ViewModel은 자신의 Output 프로퍼티에 emit > 이 프로퍼티를 observe하고있던 View에서 Entity를 UI에 표출
Presentation Layer
- Flow, ViewController, ViewModel이 존재
- ViewModel은 하나 이상의 Use case를 execute하기 때문에 Domain Layer에 의존
Domain Layer
- Domain은 Entity, UseCase, Interface 존재
Data Layer
- DB, Network 존재
- 주의: Network 기반 구현 로직은 다른 그룹에서 구현, 해당 그룹에서는 Domain에서 사용되는 부분을 구현
Presentation이 Domain에 의존하는 경우
- ViewModel(Presentation layer)이 Domain에 의존하는 형태
- ex) MoviesQueryListViewModel은 Movie의 query값을 가지고 movie를 조회하는 usecase `fetchRecentMovieQueriesUseCaseFactory` 존재하고 이 usecase를 통해 Actor가 원하는 MovieQuery값 획득
import Foundation
typealias MoviesQueryListViewModelDidSelectAction = (MovieQuery) -> Void
protocol MoviesQueryListViewModelInput {
func viewWillAppear()
func didSelect(item: MoviesQueryListItemViewModel)
}
protocol MoviesQueryListViewModelOutput {
var items: Observable<[MoviesQueryListItemViewModel]> { get }
}
protocol MoviesQueryListViewModel: MoviesQueryListViewModelInput, MoviesQueryListViewModelOutput { }
typealias FetchRecentMovieQueriesUseCaseFactory = (
FetchRecentMovieQueriesUseCase.RequestValue,
@escaping (FetchRecentMovieQueriesUseCase.ResultValue) -> Void
) -> UseCase
final class DefaultMoviesQueryListViewModel: MoviesQueryListViewModel {
private let numberOfQueriesToShow: Int
private let fetchRecentMovieQueriesUseCaseFactory: FetchRecentMovieQueriesUseCaseFactory
private let didSelect: MoviesQueryListViewModelDidSelectAction?
// MARK: - OUTPUT
let items: Observable<[MoviesQueryListItemViewModel]> = Observable([])
init(numberOfQueriesToShow: Int,
fetchRecentMovieQueriesUseCaseFactory: @escaping FetchRecentMovieQueriesUseCaseFactory,
didSelect: MoviesQueryListViewModelDidSelectAction? = nil) {
self.numberOfQueriesToShow = numberOfQueriesToShow
self.fetchRecentMovieQueriesUseCaseFactory = fetchRecentMovieQueriesUseCaseFactory
self.didSelect = didSelect
}
private func updateMoviesQueries() {
let request = FetchRecentMovieQueriesUseCase.RequestValue(maxCount: numberOfQueriesToShow)
let completion: (FetchRecentMovieQueriesUseCase.ResultValue) -> Void = { result in
switch result {
case .success(let items):
self.items.value = items.map { $0.query }.map(MoviesQueryListItemViewModel.init)
case .failure: break
}
}
let useCase = fetchRecentMovieQueriesUseCaseFactory(request, completion)
useCase.start()
}
}
// MARK: - INPUT. View event methods
extension DefaultMoviesQueryListViewModel {
func viewWillAppear() {
updateMoviesQueries()
}
func didSelect(item: MoviesQueryListItemViewModel) {
didSelect?(MovieQuery(query: item.query))
}
}
Data 레이어가 Domain에 의존하는 경우
- Domain에 UseCase가 Data의 Repository Protocol에 의존하지만, Data의 구현체가 Protocol을 의존하고 있는 형태 (DIP)
- 위 예제 코드에서 사용되는 `FetchRecentMovieQueriesUseCase`에서 repository에 접근하는 형태
// This is another option to create Use Case using more generic way
final class FetchRecentMovieQueriesUseCase: UseCase {
struct RequestValue {
let maxCount: Int
}
typealias ResultValue = (Result<[MovieQuery], Error>)
private let requestValue: RequestValue
private let completion: (ResultValue) -> Void
private let moviesQueriesRepository: MoviesQueriesRepository
init(requestValue: RequestValue,
completion: @escaping (ResultValue) -> Void,
moviesQueriesRepository: MoviesQueriesRepository) {
self.requestValue = requestValue
self.completion = completion
self.moviesQueriesRepository = moviesQueriesRepository
}
func start() -> Cancellable? {
moviesQueriesRepository.fetchRecentsQueries(maxCount: requestValue.maxCount, completion: completion)
return nil
}
}
- `FetchRecentMovieQueriesUseCase`의 구현체는 Data 레이어에서 구현된 것
- ex) DIContainer에서 주입
// MoviesSceneDIContainer
func makeFetchRecentMovieQueriesUseCase(requestValue: FetchRecentMovieQueriesUseCase.RequestValue,
completion: @escaping (FetchRecentMovieQueriesUseCase.ResultValue) -> Void) -> UseCase {
return FetchRecentMovieQueriesUseCase(requestValue: requestValue,
completion: completion,
moviesQueriesRepository: makeMoviesQueriesRepository()
)
}
func makeMoviesQueriesRepository() -> MoviesQueriesRepository {
return DefaultMoviesQueriesRepository(dataTransferService: dependencies.apiDataTransferService,
moviesQueriesPersistentStorage: moviesQueriesStorage)
}
* .drawio 파일:
* 참고
- https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
'Architecture (swift) > MVVM (개념)' 카테고리의 다른 글
Comments