์ํํธ์จ์ด ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ๋ค์ด ๋ดค์ MVC, MVP, MVVM ๋ฑ์ ์ฌ๋ฌ๊ฐ์ง ๋์์ธ ํจํด์ด ์์ต๋๋ค.
iOS๋ ๊ธฐ๋ณธ์ ์ผ๋ก MVC ์ฐจ์ฉ ํ๊ณ ์๊ธฐ ๋๋ฌธ์ MVC ๊ตฌ์กฐ๋ ๋ค๋ค ์ต์ํ๊ฒ ์ฌ์ฉ ์ค์ผ ๊ฒ๋๋ค.
(View์ ViewController๋ฅผ ์ฌ์ฉํ๋ MVC๋ผ๊ณ ์๊ฐ์๋๋ถ๋ ๊ณ์์ง๋ง ์ค์ ๋ก๋ MVP ํจํด ์
๋๋ค.)
ํด๋น ํจํด์ MVC๋ผ๊ณ ๋ถ๋ฅด์๋ ๋ถ๋ ๊ณ์
์ ํธ์์ MVC๋ผ๊ณ ํ๊ฒ ์ต๋๋ค.
ํ์ง๋ง MVC ๊ตฌ์กฐ๋ Controller๊ฐ ๊ณผ๋ํ๊ฒ ๋น๋ํด ์ง๋ ๋ฌธ์ ๊ฐ ์์๊ณ , Dooray์ฑ๋ MVC-> MVVM์ผ๋ก ์ ํ ํ๊ฒ ๋์์ต๋๋ค.
ํ์ง๋ง Dooray์ฑ์ ํน์ฑ์ ํ ํ๋ฉด์์ ๋ง์ APIํธ์ถ๊ณผ ์ฌ์ฉ์ ์กฐ์์ด ์ผ์ด ๋๊ณ ์ด์ ๋ฐ๋ผ UI๊ฐ ์์ฃผ ๋ณ๊ฒฝ ๋๋๋ฐ ๋น๋๊ธฐ ๋์๋ค์ด ๋์์ ์ผ์ด ๋๋๊ฒฝ์ฐ ์ ํํ ํ๋ฉด ์ํ๋ฅผ ํ์ ํ๋๋ฐ ์ด๋ ค์์ ๊ฒช์์ต๋๋ค.
์ด๋ฅผ ๊ทน๋ณต ํ๊ณ ์ ์ฝ 3๋
์ ๋ถํฐ MVI ํจํด์ ์ ์ฉ ํ์๊ณ ํ์ฌ๊น์ง ํ์ฉ ์ค์
๋๋ค. ๋ค์ ๋ฆ์ ๊ฐ์ด ์์ง๋ง MVI ํจํด ์ ์ฉ ๋ฐฉ๋ฒ๊ณผ ์ฅ๋จ์ ๋ฑ์ ๊ณต์ ํ๊ณ ์ ๊ธ์ ๋จ๊ธฐ๊ฒ ๋์์ต๋๋ค.
MVI๋?
iOS ๊ฐ๋ฐ์ ๋ถ์ด์๋ผ๋ฉด ๋ค์ ์์ ํ ์๋ ์๋๋ฐ์. Model-View-Intent์ ์ฝ์๋ก ๋จ๋ฐฉํฅ ์ํคํ ์ฒ ํจํด์ ๋๋ค.
๊ตฌ์กฐ๋ฅผ ๊ฐ๋จํ๊ฒ ๊ทธ๋ ค ๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค.

๊ฐ ์์์ ์ญํ ์ ์๋์ ๊ฐ์ต๋๋ค.
View: ์ฌ์ฉ์์๊ฒ ์ ๊ณต๋์ด ๋ณด์ฌ ์ง๋ UI ๋ถ๋ถ์ผ๋ก ์ํ(state)๋ฅผ ์ ๋ ฅ ๋ฐ์ ํ๋ฉด์ ์ถ๋ ฅ
Intent: ์ฑ ์ํ๋ฅผ ๋ณ๊ฒฝ ํ๋ ค๋ ์๋๋ฅผ ์๋ฏธํ๋ฉฐ ์ฌ์ฉ์์ ์ํธ์์ฉ์ผ๋ก ๋ฐ์ํ ์ํ๋ฅผ ๋ณ๊ฒฝ ํ๋ ๋์์ ํฉ๋๋ค.
Model(State): ์ฑ์ ์ํ๋ฅผ ์๋ฏธํ๋ฉฐ MVI์์๋ ํ๋์ ์ํ๋ง์ ๊ฐ์ผ๋ฉฐ imutableํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ก ์์ฑ ๋์ด์ผ ํฉ๋๋ค. intent๋ฅผ ํธ๋ฆฌ๊ฑฐ ํ์ฌ ์๋ก์ด ์ํ๋ฅผ ๋ง๋๋ ๊ฒ๋ง์ด State๋ฅผ ๋ฐ๊พธ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ๋๋ค.
๋จ๋ฐฉํฅ ์ํคํ ์ฒ?
MVI์์ ๊ฐ์ฅ ์ค์ํ ๋ด์ฉ์ด ๋จ๋ฐฉํฅ ์ํคํ ์ฒ๋ผ๋๊ฒ์ด ์๋๊น ํฉ๋๋ค. ๊ทธ๋ผ ๋จ๋ฐฉํฅ ์ดํคํ ์ฒ๋ ๋ฌด์์ผ๊น์?
๋จ๋ฐฉํฅ ์ํคํ ์ฒ๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ MVI์์์ ์ด๋ฒคํธ ํ๋ฆ์ ๋จผ์ ์ดํด ํ ํ์๊ฐ ์์ต๋๋ค.
- ์ ์ ์ ์ธํฐ๋ ์ ์ด ๋ฐ์(ํ๋ฉด ํฐ์น ๋ฑ์ ๋์)
- Intent๋ก ํด๋น Event๊ฐ ์ ๋ฌ
- Intent๊ฐ ์ํฉ์ ๋ง๋ Model(State)์ ์ ๋ฐ์ดํธ
- View์์๋ Model(State)์ ๊ด์ฐฐํ๊ณ ์๋ค๊ฐ ๋ณํ๊ฐ ์ผ์ด ๋ ๋ ๋ง๋ค ์ด๋ฅผ ๊ฐ์งํ์ฌ ์ํ์ ๋ง๋ ํ๋ฉด์ ๋ ๋๋ง ํฉ๋๋ค.
์ด๋ ๊ฐ์ฅ์ค์ ํ๊ฒ์ Model(State)๊ฐ View๋ก ์ ๋ฌ ๋ ๋ Imutableํ๋ค๋ ๊ฒ์ ๋๋ค. ์ด๋ View์์๋ Model(State)๋ฅผ ์ง์ ์ ์ผ๋ก ์์ ํ ๋ฐฉ๋ฒ์ ์กด์ฌ ํ์ง ์์์ ์๋ฏธํ๋ฉฐ Model(State)์ ์์ ํ๊ธฐ ์ํด์๋ Intent๋ฅผ ๊ฑฐ์ณ์ผ๋ง ํฉ๋๋ค. ๋ํ Intent์ ์๋ View์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ๊ณ ์์ง ์์ผ๋ฉฐ Intent์์ View๋ก Event๋ฅผ ์ ๋ฌ ํ ์ ์์ต๋๋ค.
๊ฐ๋ ์ ์ธ ์ด์ผ๊ธฐ ๋ง์ผ๋ก๋ MVI์ ๋จ๋ฐฉํฅ ๊ตฌ์กฐ์ ๋ํด์ ์ดํดํ๊ธฐ ์ด๋ ค์ธ ์๋ ์์ต๋๋ค. Dooray์์๋ MVI ํจํด์ ์ ์ฉํ๊ธฐ ์ํด ๋ช๊ฐ์ง ์คํ์์ค ํ๋ ์ ์ํฌ๋ค์ ๊ฒํ ํ์๊ณ ReactorKit์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์์ต๋๋ค. ReactorKit์ ์ ์ฉ ํ๋ฉด์ ๋ ์์ธํ ์์ ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ReactorKit?
Git ๋งํฌ: https://github.com/ReactorKit/ReactorKit
ReactorKit์ Swift์ Objective-C๊ธฐ๋ฐ์ผ๋ก ์์ฑ ๋์ด ์์ผ๋ฉฐ iOS, Mac OS ๋ฑ์์ MVI ํจํด์ ์ฌ์ฉ ํ ์ ์๋๋ก ํด์ฃผ๋ ๋จ๋ฐฉํฅ, ๋ฐ์ํ Framework์ ๋๋ค. RxSwift์ ์์กด์ฑ์ ๊ฐ๊ณ ์์ด Rx์ ๋ํ ์ ํ ์ง์์ด ํ์ ์ ์ ๋๋ค.
ReactorKit์์๋ MVI์ ๊ฐ ์์์ ์ด๋ฆ์ ์ข ๋ค๋ฅด๊ฒ ์ง์นญํ๊ณ ์์ต๋๋ค. ๋จผ์ MVI์ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆผ์ผ๋ก ๊ทธ๋ ค ๋ณด๋ฉด ์๋ ์๊ฐ์ต๋๋ค.

์ ๊ทธ๋ฆผ์ ReactorKit์ ๋์ํ๋ ํํ๋ก ๋ค์ ๊ทธ๋ ค ๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
View: MVI์ View์ ReactorKit View๋ ๋์ผํฉ๋๋ค. ReactorKit์์ View๋ ์ด๋ ํ ๋น์ง๋์ค ๋ก์ง๋ ๊ฐ๊ณ ์์ง ์์ผ๋ฉฐ ์๋ ์ ๊ฐ์ด View Protocol์ ํ์ฅํ์ฌ ์ฌ์ฉ ํฉ๋๋ค.
//View protocol์ ๊ตฌํ
class ProfileViewController: UIViewController, View {
var disposeBag = DisposeBag()
}
profileViewController.reactor = UserViewReactor() // Reactor ๊ตฌํ์ฒด๋ฅผ ์ฃผ์
View๋ UIEvent๋ค์ Action์ผ๋ก ๋ณํ ํ์ฌ Reactor.action์ ๋ฐ์ธ๋ฉ ํด์ฃผ๊ณ State๋ฅผ ๊ตฌ๋ ํ๊ณ UI์ฝคํฌ๋ํธ์ ๋ฐ์ธ๋ฉ ํ์ฌ ํ๋ฉด์ ๋ ๋๋งํ๋ ์ญํ ์ ํฉ๋๋ค.
func bind(reactor: ProfileViewReactor) {
// action (View -> Reactor)
refreshButton.rx.tap.map { Reactor.Action.refresh }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
// state (Reactor -> View)
reactor.state.map { $0.isFollowing }
.bind(to: followButton.rx.isSelected)
.disposed(by: self.disposeBag)
}
Action: View์์ Reactor๋ก ์ ๋ฌ ๋๋ Event๋ฅผ ReactorKit์์๋ Action์ผ๋ก ์ง์นญ ํฉ๋๋ค.
Reactor: MVI์ Intent์ ๋์ํ๋ ๊ฐ๋
์ผ๋ก ๋ด๋ถ์ ์ผ๋ก UI์๋ ๋
๋ฆฝ์ ์ธ ๋ ์ด์ด์ ์ํด ์์ผ๋ฉฐ State๋ฅผ ์
๋ฐ์ดํธํ๊ณ ๊ด๋ฆฌ ํฉ๋๋ค. ๋ชจ๋ ๋ทฐ์๋ ํด๋น Reactor๊ฐ ์์ผ๋ฉฐ ๋ชจ๋ ๋ก์ง์ ํด๋น Reactor์ ์์ํฉ๋๋ค. Reactor๋ ๋ทฐ์ ์ข
์๋์ง ์์ผ๋ฏ๋ก ์ฝ๊ฒ ํ
์คํธํ ์ ์์ต๋๋ค.
Reactor๋ฅผ ๊ตฌํ ํ๊ธฐ์ํด์๋ ReactorKit์ ์ ์ ๋์ด ์๋ Reactor Protocol์ ์๋์ ๊ฐ์ด ํ์ฅ ํ๋ฉด ๋ฉ๋๋ค.
Action, Mutation, State์ ์ธ๊ฐ์ง ๋ฐ์ดํฐ ์ ํ์ ์ ์ ํด์ผ ํ๋ฉฐ ์ต์ด ์ํ๋ฅผ ์๋ฏธํ๋ initialState๋ฅผ ์ ์ ํ์ฌ์ผ ํฉ๋๋ค.
// <์ฝ๋1.>
class ProfileViewReactor: Reactor {
// represent user actions
enum Action {
case refreshFollowingStatus(Int)
case follow(Int)
}
// represent state changes
enum Mutation {
case setFollowing(Bool)
}
// represents the current view state
struct State {
var isFollowing: Bool = false
}
let initialState: State = State()
}
์์์ Action์ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ๋ํ๋ด๊ณ State๋ ํ๋ฉด์ ๋ํ๋ด๊ธฐ ์ํ ์ํ๋ผ๊ณ ์ด์ผ๊ธฐ ํ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ Mutation์ด๋ผ๋ ์๋ก์ด ๊ฐ๋ ์ด ๋ฑ์ฅํ๋๋ฐ์. ๊ฐ๋จํ๊ฒ ์ค๋ช ํ๋ฉด Action๊ณผ State์ ์ค๊ฐ ๋ค๋ฆฌ์ญํ ์ ํ๋ ์ด๋ฒคํธ๋ผ๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค. ์๋์ ๊ทธ๋ฆผ์ ๋ณด๋ฉด์ ๋ ์์ธํ๊ฒ ์ค๋ช ํ๋๋ก ํ๊ฒ ์ต๋๋ค.

View์์๋ Action์ ๋ง๋ค์ด์ Reactor์ ์ ๋ฌํ๊ฒ๋๊ณ ์ด๋ Action์ ์์ ํ๋๊ณณ์ด mutate() ํจ์ ์
๋๋ค.
mutate() ํจ์๋ API์์ฒญ ๊ฐ์ ๋น๋๊ธฐ ์์ ๋ค์ ์ฒ๋ฆฌ ํ๊ฒ ๋๊ณ ํด๋น ์์ ์ด ์๋ฃ ๋๋ฉด Mutation์ ๋ฐ์ ์์ผ reduce() ํจ์๋ก ์ ๋ฌํฉ๋๋ค.
SideEffect: ์ธ๋ถ ์ธ๊ณ์ ์ํฅ์ ์ฃผ๊ณ ๋ฐ๋ ์ผ๋ จ์ ๊ณผ์ ์ผ๋ก APIํต์ , DB ์ฌ์ฉ ๋ฑ์ด ์ด์ ์ํ๋ค๊ณ ๋ณผ ์ ์์ผ๋ฉฐ ๋๋ถ๋ถ์ SideEffect๋ ๋น๋๊ธฐ ์์ ์ผ๋ก ์ํ ๋๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ SideEffect๋ ReactorKit์์๋ mutate()ํจ์์์๋ง ์ทจ๊ธ ํ๋๋ก ๋์ด ์์ต๋๋ค.
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case let .refreshFollowingStatus(userID): // receive an action
return UserAPI.isFollowing(userID) // create an API stream
.map { (isFollowing: Bool) -> Mutation in
return Mutation.setFollowing(isFollowing) // convert to Mutation stream
}
case let .follow(userID):
return UserAPI.follow()
.map { _ -> Mutation in
return Mutation.setFollowing(true)
}
}
}
func reduce(state: State, mutation: Mutation) -> State {
var state = state // create a copy of the old state
switch mutation {
case let .setFollowing(isFollowing):
state.isFollowing = isFollowing // manipulate the state, creating a new state
return state // return the new state
}
}
์ด๋ฌํ ํ๋ฆ์ ๋ณด๋ฉด ๋ช๊ฐ์ง ์๋ฌธ์ด ์๊ธธ ์ ์์ต๋๋ค.
1. ๋น๋๊ธฐ ์์ ์ ์ mutate()ํจ์์์ ์ฒ๋ฆฌํด์ผ ํ๋๊ฐ?
-> ๋น๋๊ธฐ ์์
์ ์คํ๋ ์ค๋ ๋์ ์๋ต ์ค๋ ๋๊ฐ ๋์ผํจ์ ๋ณด์ฅ ํ์ง ์์ต๋๋ค.
์ด ๋ง์ ๋ง์ฝ reduce()์์ ์ฌ๋ฌ๊ฐ์ ๋น๋๊ธฐ ์์
์ด ์ํ ๋๋ ๊ฒฝ์ฐ ๋์์ ์ฌ๋ฌ ์ค๋ ๋์์ state์ ์ ๊ทผ ํ๊ฒ ๋ฉ๋๋ค. ์ด ๊ฒฝ์ฐ Action -> Reactor -> State์ ์คํธ๋ฆผ์ ์น๋ช
์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
์๋ฅผ ๋ค๋ฉด A์์
์ด A์ค๋ ๋์์ ๋์ํ๊ณ , B์์
์ด B์ค๋ ๋์์ ์๋ํ๋ค๊ณ ๊ฐ์ ํฉ์๋ค. A์์
์ด ์๋ฃ๋์ด A์ค๋ ๋์์ State๋ฅผ ์
๋ฐ์ดํธ ํ๋ ์ค B์ค๋ ๋์์
์ด ์๋ฃ ๋์ด ๋์์ B์ค๋ ๋์์ State์ ์ ๊ทผํ๋ ์ํฉ์ด ๋ฐ์ ํ ์ ์๊ณ ์ด๋ State์ ์์์ฑ์ ๋ณด์ฅ ํ ์ ์๊ฒ ๋จ ์ ์๋ฏธ ํฉ๋๋ค.
์ด์ ReactorKit์์๋ ๋จ์ผ ์ค๋ ๋์์๋ง ๋์ํ๋ reduce()ํจ์๋ฅผ ๋ง๋ค์ด์ state์ ๋์์ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ์ ๊ทผ ํ๋ ์ํฉ์ ๋ง๊ณ ์์ต๋๋ค.
2. reduce() ํจ์์์์ ์๋ก์ด state๋ฅผ ์์ฑํ๋๊ฐ?
-> ์ด๋ฐ์ State๋ imutableํ๋ค๊ณ ํ์์ต๋๋ค. ์์ <์ฝ๋1>
์ ๋ณด์๋ฉด Reactor์ ๊ตฌํ์ฒด์์ State๋ Struct๋ก ์ฌ์ฉ ํ๋๊ฒ์ ๋ณด์ค ์ ์์ต๋๋ค. ์ด๋ Reactor-> View๋ก State๊ฐ ์ ๋ฌ ๋๋๊ฒฝ์ฐ ๋ณต์ฌ๊ฐ ์ผ์ด ๋๊ฒ ๋๊ณ View์์ State๋ฅผ ๋ฐ๊พธ๋๋ผ๋ ์ด๋ Reactor๋ด๋ถ์ State๋ ๋ฐ๋์ง ์์์ ์๋ฏธํฉ๋๋ค. ์ฆ, View์์๋ state์ ๋ํ ์ง์ ์ ์ธ ์ฐธ์กฐ๋ฅผ ๊ฐ๊ธฐ ์๊ฒ ๋์ด View์์ state๋ฅผ ๋ฐ๊พธ๋ ์ ์ฌ์ ์ธ ์ํ์ผ๋ก ๋ถํฐ ์์ ํฉ๋๋ค.
State: MVI ์ Model ๋ก์ View๋ State๋ฅผ ์ฐธ๊ณ ํ์ฌ ํ๋ฉด์ ๋ ๋๋ง ํ๊ฒ ๋ฉ๋๋ค. ReactorKit ์์๋ ๋ณดํต State๋ ๊ตฌ์กฐ์ฒดํํ๋ก ์ฌ์ฉํ๊ณ ์ด๋ state๋ฅผ imutableํ๊ฒ ๋ง๋๋ ํจ๊ณผ๋ฅผ ๊ฐ๊ณ ์์ต๋๋ค.
์ฌ๊ธฐ ๊น์ง ๋๋ต์ ์ธ MVIํจํด๊ณผ ReactorKit์ ๋์ ๋ฐฉ์์ ๋ํด ์์ ๋ณด์์ต๋๋ค. ๋ค์์๋ Dooray์์ ์ฌ์ฉํ๋ ์ค์ ์ฝ๋๋ฅผ ์์๋ก transform๊ณผ 1ํ์ฑ ์ด๋ฒคํธ ๋ค์ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ฐธ๊ณ ๋ฌธํ:
https://github.com/ReactorKit/ReactorKit