네이버 부스트캠프 인터미션 기간 동안 공부해보면서 알게 된 내용에 대해 정리하고자 한다.
사실 이전까지 Model에 대해 깊게 생각해본 적이 없었다.
그저 Model은 내가 사용하는 데이터의 형태를 의미하고, 심지어 ViewModel에서 비즈니스 로직을 다루는 것으로 잘못 알고 있었다.
이에 대한 오류를 바로 잡기 위해 MVVM에 대해 공부를 해보고자 한다.
MVVM이란?
- MVVM이란 Model-View-ViewModel의 약자로, 프로그램의 Business 로직과 Presentation 로직을 분리하는 디자인 패턴이다.
- MVC 패턴의 Massive ViewController와 같은 문제를 해결할 수 있다.
Model
- MVVM에서 Model은 데이터를 다루는 부분과 비즈니스 로직을 포함한다.
- Model은 앱의 데이터와 비즈니스 규칙을 정의한다. 아래의 항목과 같은 기능을 한다.
- 데이터 구조 정의
- 데이터 유효성 검증
- 비즈니스 규칙 구현
- 데이터 저장소(Repository) 관련 로직
- 네트워크 통신 로직
- Model은 View나 ViewModel에 대해 전혀 알지 못하며 독립적으로 동작해야 한다.
iOS에서 View와 ViewController는 같은 레이어에 속한다.
- UIKit 컴포넌트들의 레이아웃과 구성
- 사용자 입력 이벤트의 처리
- ViewModel과의 데이터 바인딩
- UI 상태 업데이트
ViewController
- View의 생명주기 관리
- UI 컴포넌트 초기화 및 설정
- ViewModel과의 바인딩 설정
- 간단한 UI 로직만 포함
나는 View, ViewController에 차이를 둔다고 생각하지 않는다. 오랜 시간 고민을 했지만, 결국 다른 부분이라고 납득할 포인트를 찾지 못했다.
ViewModel
- View와 Model 사이의 중재자 역할을 한다다.
- Presentation 로직을 담당한다.
- Model의 데이터를 View에 적합한 형태로 변환
- View의 상태 관리
- 사용자 인터랙션에 대한 처리 로직
- Model의 데이터 업데이트 요청
그렇다면 ViewModel과 View(ViewController)를 어떻게 바인딩을 할 수 있을까?
우선 바인딩이란 뭘까?
- View-ViewModel 사이의 데이터를 동기화 하는 과정을 의미한다.
iOS에서는 어떻게 바인딩을 구현할 수 있을까?
- Closure / Delegate
- Combine
- RxSwift / RxCocoa
- Observable
- @Published …
[이번 글에서는 인터미션 때 진행했던 프로젝트를 기반으로 작성할 예정이다. 예시는 모두 프로젝트를 진행하며 작성된 코드 중 일부를 가져온 내용이다.]
프로젝트 내용
- Github Access Token을 얻게 된 후, TextField에 Github 아이디를 입력하면 그에 해당하는 리스트를 출력하는 내용이다
- 최종 버전
GitHub - ParkSeongGeun/GithubUserTracker: - Practice UIKit
- Practice UIKit. Contribute to ParkSeongGeun/GithubUserTracker development by creating an account on GitHub.
github.com
기존 바인딩 과정
처음 작성했던 MVVM 패턴에서는 RxSwift를 사용하여 ViewModel과 View를 직접 바인딩하였다.
하지만 RxSwift와 MVVM을 프로젝트에 적용시키기 위해 구글링을 하던 과정에서 Input / Output 패턴을 접하게 되었고, 보편적으로 사용되는 패턴 중 하나라는 것을 알게 되었다.
Input / Output 패턴을 적용하게 되면서 알게 된 것을 정리해보고자 한다.
Input/Output 패턴?
Input/Output 패턴은 ViewModel의 인터페이스를 세 가지 주요 부분으로 정의한다.

- ViewModelType과 같은 프로토콜을 통해 ViewModel 내에서 Input과 Output의 형태를 정의할 수 있도록 associatedType으로 지정하고 transform 함수를 구현하도록 선언한다.
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
저게 다 뭔데?
1) Input
- View/ViewController의 이벤트를 감지하여 ViewModel에 보낼 데이터를 의미한다.
- 각 필드는 Observable 스트림으로 정의
struct Input {
let tabButtonType: Observable<TabButtonType>
let query: Observable<String>
let saveFavorite: Observable<UserListItem>
let deleteFavorite: Observable<Int>
let fetchMore: Observable<Void>
}
2) Output
- View에 표시될 모든 데이터 스트림을 정의한다.
- UI 상태 변화에 대한 스트림 포함
- 에러 처리를 위한 스트림 포함
struct Output {
let cellData: Observable<[UserListCellData]>
let error: Observable<String>
}
3) transform
- Input을 Output으로 변환하는 비즈니스 로직을 포함한다.
- 단방향 데이터 흐름 보장
- 모든 스트림 조합과 변환을 한 곳에서 처리
func transform(input: Input) -> Output {
input.query.bind {
...
}.disposed(by: disposeBag)
// TODO: 즐겨찾기 추가
input.saveFavorite
.withLatestFrom(input.query, resultSelector: { users, query in
return (users, query)
})
.bind { [weak self] user, query in
self?.saveFavoriteUser(user: user, query: query)
}.disposed(by: disposeBag)
// TODO: 즐겨찾기 삭제
input.deleteFavorite
.withLatestFrom(input.query, resultSelector: { ($0, $1)} )
.bind { [weak self] userID, query in
self?.deleteFavoriteUser(userID: userID, query: query)
}.disposed(by: disposeBag)
// TODO: 다음 페이지 검색
input.fetchMore
.withLatestFrom(input.query)
.bind {
...
}.disposed(by: disposeBag)
...
return Output(
cellData: cellData,
error: error.asObservable().observe(on: MainScheduler.instance)
)
}
Input / Output 패턴의 장점
- 일반적인 MVVM 패턴과 다르게 Input/Output 패턴은 바인딩 부분을 하나의 영역에서 관리할 수 있었다.
- 사용자 interAction에 따른 Input, Output을 정의해 사용하기 때문에 로직 분리가 용이해진다.
- 다른 개발자가 봤을 때 Input, Output 객체만 보고도 화면에서 행해지는 액션들을 알 수 있어 유지보수에 용이하다.
- ViewController, ViewModel을 확실히 분리해 의존성을 줄여줄 수 있다.
let output = viewModel.transform(
input: UserListViewModel.Input(
tabButtonType: tabButtonType,
query: query,
saveFavorite: saveFavorite.asObservable(),
deleteFavorite: deleteFavorite.asObservable(),
fetchMore: fetchMore.asObservable()
)
결론
- 모든 상황에 대해 Input / Output 패턴을 적용하는 것은 너무 큰 부담을 앉고 시작하는 것 같다.
- 만약, Input / Output을 정의하는 경우 자체가 적을 시엔 굳이 적용하나 싶다.
- 단, 화면에서 정의되는Interaction이 많고, 각각의 상황에 대해 binding을 진행해야 된다면 유지보수, 가독성을 위해서 적용하는 것이 편리할 것 같다.
- 결국 패턴이라 함은 결국 유지보수를 용이하게 하기 위함이라고 다시 한 번 느낀다.
앞으로의 할 일
기존 Combine을 이용한 binding 을 진행했었는데, RxSwift를 처음하면서 Rx / Input-Output 을 하다보니 Rx에 관한 깊은 학습은 진행하지 못해서 아쉬웠다.
- RxSwift 정리
- Combine을 Input / Output 패턴에서 사용할 수도 있을까? How?
- Clean-Architecture 정리 (2)
'iOS' 카테고리의 다른 글
MVVM을 직접 사용해보며 (0) | 2024.11.10 |
---|---|
단방향 / 양방향 데이터 흐름 (0) | 2024.11.10 |
Clean Architecture 적용해보기 (2) | 2024.09.29 |
추상화, 그리고 타입 캐스팅? (0) | 2024.09.10 |
의존성, 의존성 주입?(2) (2) | 2024.09.09 |
네이버 부스트캠프 인터미션 기간 동안 공부해보면서 알게 된 내용에 대해 정리하고자 한다.
사실 이전까지 Model에 대해 깊게 생각해본 적이 없었다.
그저 Model은 내가 사용하는 데이터의 형태를 의미하고, 심지어 ViewModel에서 비즈니스 로직을 다루는 것으로 잘못 알고 있었다.
이에 대한 오류를 바로 잡기 위해 MVVM에 대해 공부를 해보고자 한다.
MVVM이란?
- MVVM이란 Model-View-ViewModel의 약자로, 프로그램의 Business 로직과 Presentation 로직을 분리하는 디자인 패턴이다.
- MVC 패턴의 Massive ViewController와 같은 문제를 해결할 수 있다.
Model
- MVVM에서 Model은 데이터를 다루는 부분과 비즈니스 로직을 포함한다.
- Model은 앱의 데이터와 비즈니스 규칙을 정의한다. 아래의 항목과 같은 기능을 한다.
- 데이터 구조 정의
- 데이터 유효성 검증
- 비즈니스 규칙 구현
- 데이터 저장소(Repository) 관련 로직
- 네트워크 통신 로직
- Model은 View나 ViewModel에 대해 전혀 알지 못하며 독립적으로 동작해야 한다.
iOS에서 View와 ViewController는 같은 레이어에 속한다.
- UIKit 컴포넌트들의 레이아웃과 구성
- 사용자 입력 이벤트의 처리
- ViewModel과의 데이터 바인딩
- UI 상태 업데이트
ViewController
- View의 생명주기 관리
- UI 컴포넌트 초기화 및 설정
- ViewModel과의 바인딩 설정
- 간단한 UI 로직만 포함
나는 View, ViewController에 차이를 둔다고 생각하지 않는다. 오랜 시간 고민을 했지만, 결국 다른 부분이라고 납득할 포인트를 찾지 못했다.
ViewModel
- View와 Model 사이의 중재자 역할을 한다다.
- Presentation 로직을 담당한다.
- Model의 데이터를 View에 적합한 형태로 변환
- View의 상태 관리
- 사용자 인터랙션에 대한 처리 로직
- Model의 데이터 업데이트 요청
그렇다면 ViewModel과 View(ViewController)를 어떻게 바인딩을 할 수 있을까?
우선 바인딩이란 뭘까?
- View-ViewModel 사이의 데이터를 동기화 하는 과정을 의미한다.
iOS에서는 어떻게 바인딩을 구현할 수 있을까?
- Closure / Delegate
- Combine
- RxSwift / RxCocoa
- Observable
- @Published …
[이번 글에서는 인터미션 때 진행했던 프로젝트를 기반으로 작성할 예정이다. 예시는 모두 프로젝트를 진행하며 작성된 코드 중 일부를 가져온 내용이다.]
프로젝트 내용
- Github Access Token을 얻게 된 후, TextField에 Github 아이디를 입력하면 그에 해당하는 리스트를 출력하는 내용이다
- 최종 버전
GitHub - ParkSeongGeun/GithubUserTracker: - Practice UIKit
- Practice UIKit. Contribute to ParkSeongGeun/GithubUserTracker development by creating an account on GitHub.
github.com
기존 바인딩 과정
처음 작성했던 MVVM 패턴에서는 RxSwift를 사용하여 ViewModel과 View를 직접 바인딩하였다.
하지만 RxSwift와 MVVM을 프로젝트에 적용시키기 위해 구글링을 하던 과정에서 Input / Output 패턴을 접하게 되었고, 보편적으로 사용되는 패턴 중 하나라는 것을 알게 되었다.
Input / Output 패턴을 적용하게 되면서 알게 된 것을 정리해보고자 한다.
Input/Output 패턴?
Input/Output 패턴은 ViewModel의 인터페이스를 세 가지 주요 부분으로 정의한다.

- ViewModelType과 같은 프로토콜을 통해 ViewModel 내에서 Input과 Output의 형태를 정의할 수 있도록 associatedType으로 지정하고 transform 함수를 구현하도록 선언한다.
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
저게 다 뭔데?
1) Input
- View/ViewController의 이벤트를 감지하여 ViewModel에 보낼 데이터를 의미한다.
- 각 필드는 Observable 스트림으로 정의
struct Input {
let tabButtonType: Observable<TabButtonType>
let query: Observable<String>
let saveFavorite: Observable<UserListItem>
let deleteFavorite: Observable<Int>
let fetchMore: Observable<Void>
}
2) Output
- View에 표시될 모든 데이터 스트림을 정의한다.
- UI 상태 변화에 대한 스트림 포함
- 에러 처리를 위한 스트림 포함
struct Output {
let cellData: Observable<[UserListCellData]>
let error: Observable<String>
}
3) transform
- Input을 Output으로 변환하는 비즈니스 로직을 포함한다.
- 단방향 데이터 흐름 보장
- 모든 스트림 조합과 변환을 한 곳에서 처리
func transform(input: Input) -> Output {
input.query.bind {
...
}.disposed(by: disposeBag)
// TODO: 즐겨찾기 추가
input.saveFavorite
.withLatestFrom(input.query, resultSelector: { users, query in
return (users, query)
})
.bind { [weak self] user, query in
self?.saveFavoriteUser(user: user, query: query)
}.disposed(by: disposeBag)
// TODO: 즐겨찾기 삭제
input.deleteFavorite
.withLatestFrom(input.query, resultSelector: { ($0, $1)} )
.bind { [weak self] userID, query in
self?.deleteFavoriteUser(userID: userID, query: query)
}.disposed(by: disposeBag)
// TODO: 다음 페이지 검색
input.fetchMore
.withLatestFrom(input.query)
.bind {
...
}.disposed(by: disposeBag)
...
return Output(
cellData: cellData,
error: error.asObservable().observe(on: MainScheduler.instance)
)
}
Input / Output 패턴의 장점
- 일반적인 MVVM 패턴과 다르게 Input/Output 패턴은 바인딩 부분을 하나의 영역에서 관리할 수 있었다.
- 사용자 interAction에 따른 Input, Output을 정의해 사용하기 때문에 로직 분리가 용이해진다.
- 다른 개발자가 봤을 때 Input, Output 객체만 보고도 화면에서 행해지는 액션들을 알 수 있어 유지보수에 용이하다.
- ViewController, ViewModel을 확실히 분리해 의존성을 줄여줄 수 있다.
let output = viewModel.transform(
input: UserListViewModel.Input(
tabButtonType: tabButtonType,
query: query,
saveFavorite: saveFavorite.asObservable(),
deleteFavorite: deleteFavorite.asObservable(),
fetchMore: fetchMore.asObservable()
)
결론
- 모든 상황에 대해 Input / Output 패턴을 적용하는 것은 너무 큰 부담을 앉고 시작하는 것 같다.
- 만약, Input / Output을 정의하는 경우 자체가 적을 시엔 굳이 적용하나 싶다.
- 단, 화면에서 정의되는Interaction이 많고, 각각의 상황에 대해 binding을 진행해야 된다면 유지보수, 가독성을 위해서 적용하는 것이 편리할 것 같다.
- 결국 패턴이라 함은 결국 유지보수를 용이하게 하기 위함이라고 다시 한 번 느낀다.
앞으로의 할 일
기존 Combine을 이용한 binding 을 진행했었는데, RxSwift를 처음하면서 Rx / Input-Output 을 하다보니 Rx에 관한 깊은 학습은 진행하지 못해서 아쉬웠다.
- RxSwift 정리
- Combine을 Input / Output 패턴에서 사용할 수도 있을까? How?
- Clean-Architecture 정리 (2)
'iOS' 카테고리의 다른 글
MVVM을 직접 사용해보며 (0) | 2024.11.10 |
---|---|
단방향 / 양방향 데이터 흐름 (0) | 2024.11.10 |
Clean Architecture 적용해보기 (2) | 2024.09.29 |
추상화, 그리고 타입 캐스팅? (0) | 2024.09.10 |
의존성, 의존성 주입?(2) (2) | 2024.09.09 |