์๋
ํ์ธ์ Foma ๐ ์
๋๋ค!
์ค๋์ RxSwift์ MVVM ๋์์ธ ํจํด์ ์ฌ์ฉํ ๋ ์์ฃผ ์ ์ฉํ๊ฒ ์ฐ์ด๋ ReactorKit์ ๋ํด์ ์ ๋ฆฌํ๋ ค๊ณ ํฉ๋๋ค!
๋ฐ๋ก ์์ํ ๊ฒ์~
ReactorKit์ด๋?
ReactorKit ๊ณต์ ๊นํ๋ธ์์๋ ์ด๋ ๊ฒ ์๊ฐํ๊ณ ์์ต๋๋ค.
ReactorKit์ ๋ฐ์ํ ๋ฐ ๋จ๋ฐฉํฅ Swift ์ ํ๋ฆฌ์ผ์ด์ ์ํคํ ์ฒ๋ฅผ์ํ ํ๋ ์ ์ํฌ์ ๋๋ค.
๊ฐ๋จํ๊ฒ ์ค๋ช
๋๋ฆฌ๋ฉด ReactorKit์ ์๋์ ๊ฐ์ด Reactor์ View๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋๋ฐ
์ฐ๊ฒฐํ๋ ๋ฐฉ์์ Action๊ณผ State๋ก ๋๋ ๊ฒ์
๋๋ค.
ViewModel ์ญํ ์ Reactor๊ฐ ํ๋๋ฐ ์ด ViewModel์์ ์ผ์ด๋๋ ๋ฐ์ํ ์ด๋ฒคํธ๋ค์ ์ก์
๊ณผ ์ํ๋ก ๋๋ ๊ฒ์ด์ฃ !
ReactorKit์ ์ฐ๋ฉด ๋ญ๊ฐ ์ข์๋ฐ?
1. ํ
์คํธํ๊ธฐ ์ฝ๋ค.
๋ทฐ์์ ๋น์ง๋์ค ๋ก์ง์ ๋ถ๋ฆฌํด๋ด ๋ฆฌ์กํฐ๋ ๋ทฐ์ ๋ํ ์ข
์์ฑ์ด ์์ด์ง๋๋ค.
๊ณ ๋ก ๋ฆฌ์กํฐ์ ๋ทฐ๋ฅผ ๋ฐ์ธ๋ฉํ๋ ํ๋ ์ฝ๋๋ง ํ
์คํธํ๋ฉด ๋๊ณ ํ
์คํธํ๊ธฐ๊ฐ ํจ์ฌ ์์ํด์ง๋๋ค.
2. ๋ถ๋ถ์ ์ผ๋ก ์์ํ ์ ์๋ค.
๋ฆฌ์กํฐ ํท์ "์ ์ฒด์ ์ธ ๋์์ธ ํจํด์ ์ด ๋ฆฌ์กํฐํท์ผ๋ก ํด!!!" ๊ฐ ์๋๋ผ
"๋ ์ฌ์ฉํ๊ณ ์ถ์ ๊ณณ์๋ง ๋ถ๋ถ์ ์ผ๋ก ์ฌ์ฉํด๋ ๋ผ" ๋ผ๊ณ ํ๊ณ ์์ต๋๋ค.
๊ณ ๋ก ๋ฆฌ์กํฐํท์ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋ค์ ์์ฑํ ํ์๋ ์๋ค๋ ๊ฒ์ด์ฃ .
3. ์ฝ๋๊ฐ ๊ฐ๊ฒฐํด์ง๋ค.
๋ฆฌ์กํฐํท์ ๋จ์ํ ์ผ์ ์ํด ๋ณต์กํ ์ฝ๋๋ฅผ ํผํ๋ ๊ฒ์ด ์์ ๋ค์ ํต์ฌ์ด๋ผ๊ณ ํฉ๋๋ค.
์ค์ ๋ก ๋ค๋ฅธ ์ํคํ
์ณ์ ๋นํด ํจ์ฌ ์ ์ ์ฝ๋๊ฐ ์ฌ์ฉ๋ฉ๋๋ค.
Example
๋ฆฌ์กํฐํท์ ๋์์๋ ๊ณต์ ์์ ๋ฅผ ํตํด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ดํดํด๋๋ก ํฉ์๋ค!
๊ฐ๋จํ๊ฒ ๊ตฌ์กฐ๋ฅผ ์ค๋ช
๋๋ฆฌ๋ฉด ํ๋ฉด์ ๋์ฐ๋ ๋ทฐ(๋ทฐ์ปจํธ๋กค๋ฌ)์ ๊ทธ ํ๋ฉด์ ๋ํ ๋น์ง๋์ค๋ฅผ ๋ก์ง์ ์์ฑํ
๋ฆฌ์กํฐ๊ฐ ์กด์ฌํฉ๋๋ค.
Pod
ํ ํ์ผ์ ์๋์ ๊ฐ์ด ReactorKit๊ณผ RxCocoa๋ฅผ ์ถ๊ฐํด์ฃผ๊ณ pod install์ ํด์ค๋๋ค.
pod 'ReactorKit'
pod 'RxCocoa'
Storyboard
๋ทฐ๋ ํ๋ฌ์ค ๋ฒํผ๊ณผ ๋ง์ด๋์ค ๋ฒํผ ์ซ์๋ฅผ ๋์ธ ๋ ์ด๋ธ ๋ก๋ฉ์ ๋ณด์ฌ์ค ์ธ๋์ผ์ดํฐ๊ฐ ์์ต๋๋ค.
CounterViewController
๋จผ์ ํ์ํ ํ๋ ์์ํฌ๋ค์ import ํด์ค๋๋ค.
import UIKit
import ReactorKit
import RxCocoa
์คํ ๋ฆฌ๋ณด๋์ ๋ทฐ์ปจํธ๋กค๋ฌ๋ฅผ ์ฐ๊ฒฐํด์ค๋๋ค.
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
@IBOutlet weak var plusButton: UIButton!
@IBOutlet weak var numberLabel: UILabel!
@IBOutlet weak var minusButton: UIButton!
CounterReactor
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ฆฌ์กํฐํท์ ํต์ฌ์ด ๋ฆฌ์กํฐ๋ฅผ ๋ง๋ค์ด๋ด
์๋ค.
๊ฐ์ฅ ๋จผ์ ReactorKit์ import ํด์ฃผ๊ณ Reactor๋ฅผ ์ฑํํด์ค๋๋ค.
import ReactorKit
class CounterViewReactor:Reactor { ...
๊ฐ์ฅ ๋จผ์ ์ก์
๋ถํฐ enum์ผ๋ก ์ ์ธํด์ค๋๋ค.
Action์ ๋ทฐ์์ ์ฐ์ด๋ ์ก์
๋ค์ ๋์ดํด์ฃผ๋ฉด ๋ผ์.
์ฐ๋ฆฌ๋ +๋ฒํผ๊ณผ -๋ฒํผ์ด ์์ผ๋ +๋ฒํผ์ ๋๋ฅด๋ ์ก์
-๋ฒํผ์ ๋๋ฅด๋ ์ก์
์ ๋ง๋ค์ด์ฃผ๋ ๊ฒ์ด์ฃ .
//์ ์ ์๊ฒ ๋ฐ์ ์ก์
enum Action {
case plus
case minus
}
์ก์
์ ๋ฐ์์ ์ด๋ ํ ๋ณํ๊ฐ ์ผ์ด๋๋์ง๋ enum์ผ๋ก ์ ์ธํด์ค๋๋ค.
์ก์
์ ๋ฐ๋ผ์ ๊ฐ์ ์ฆ๊ฐ ์ํค๋ ๊ฒ๊ณผ ๊ฐ์ ๊ฐ์ ์ํค๋ ๊ฒ ๊ทธ๋ฆฌ๊ณ ๋ก๋ฉ์ ์ด๋ป๊ฒ ๋ํ๋ผ๊ฑด์ง์ ๋ํ
๋ณํ๋ค์ ๋์ดํด์ฃผ๋ ๊ฒ์
๋๋ค.
//์ก์
์ ๋ฐ์ ์ด๋ ํ ๋ณํ
enum Mutation {
case increaseValue
case decreaseValue
case setLoading(Bool)
}
์ด์ ๋ณํ๋ค์ ๋ํ ๊ฐ์ ์ ์ฅํ ์ํ๊ฐ ์์ด์ผ๊ฒ ์ฃ ?
๋ํ๊ฑฐ๋ ๋บ์ ๋์ ์ํ๋ฅผ ์ ์ฅํ number์ ๊ทธ ์ฌ์ด์ ๋ก๋ฉ์ ๋ณด์ฌ์ค ๋ก๋ฉ์ ์ํ๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
//์ด๋ ํ ๋ณํ๋ฅผ ๋ฐ์ ์ํ
struct State {
var number:Int
var isLoading:Bool
}
์ด์ ๋ถํฐ๋ ๋ฉ์๋๋ค์ ๋ง๋ค์ด์ค๊ฑด๋ฐ์.
์ก์
์ ๋ฐ์์ ๋ ๋ณํ๋ฅผ ์คํํ mutate ์
๋๋ค. (Action -> Mutation)
์๋๋ฅผ ๋ณด๋ฉด ๊ฐ ์ก์
์ ๋ฐ๋ผ ์ ํฉํ ๋ฎคํ
์ด์
์ด๋ฒคํธ๋ค์ด ๋ฐ์๋ฉ๋๋ค.
//์ก์
์ ๋ง๊ฒ ๋ณํํด
func mutate(action:Action) -> Observable<Mutation> {
switch action {
case .plus:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.increaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
case .minus:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.decreaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
}
}
์ mutate์์ ๋ฐ์ํ ์ด๋ฒคํธ๋ค์ ๋ฐ์์ ๊ฐ์ ์ํ๋ฅผ ๋ณํ์์ผ์ค reduce ์
๋๋ค. (Mutation -> State)
๋ณํ๋ ์ด๋ฒคํธ๋ค์ ๋ฐ๋ผ์ ๊ฐ์ ์ ํฉํ๊ฒ ๋ฐ๊ฟ์ค๋๋ค.
//๋ณํ์ ๋ง๊ฒ ๊ฐ์ด ์ค์ ํด
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case .increaseValue:
state.number += 1
case .decreaseValue:
state.number -= 1
case let .setLoading(isLoading):
state.isLoading = isLoading
}
return state
}
CounterViewController
์ด์ ๋ทฐ์ ๋ฆฌ์กํฐ๋ฅผ ์ฐ๊ฒฐํด์ฃผ์ด์ผ๊ฒ ์ฃ ?
๊ฐ์ฅ ๋จผ์ ๋์คํฌ์ฆ๋ฐฑ๊ณผ ์์์ ๋ง๋ค์ด์ค CounterViewReactor๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
let disposeBag:DisposeBag = DisposeBag()
let counterViewReactor:CounterViewReactor = CounterViewReactor()
์ด์ ๋ฆฌ์กํฐ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ bind ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
func bind(reator:CounterViewReactor) {...}
bind ๋ฉ์๋ ์์ ๊ฐ ๋ฒํผ๊ณผ ๋ฆฌ์กํฐ์ ์ก์
์ ๋ฐ์ธ๋ฉํด์ค๋๋ค.
//+๋ฒํผ๊ณผ reactor์ action์ ๋ฐ์ธ๋ฉ
plusButton
.rx
.tap
.map{CounterViewReactor.Action.plus}
.bind(to: reator.action)
.disposed(by: disposeBag)
//-๋ฒํผ๊ณผ reactor์ action์ ๋ฐ์ธ๋ฉ
minusButton
.rx
.tap
.map{CounterViewReactor.Action.minus}
.bind(to: reator.action)
.disposed(by: disposeBag)
bind ๋ฉ์๋ ์์ ๋ฆฌ์กํฐ์ ์ํ๊ฐ๊ณผ ๋ทฐ์ ์ซ์๋ฅผ ๋ํ๋ผ ๋ ์ด๋ธ๊ณผ ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํด์ค๋๋ค.
//reacotor์ value์ numberLabel์ ๋ฐ์ธ๋ฉ
reator.state
.map {"\($0.number)"}
.distinctUntilChanged()
.bind(to: numberLabel.rx.text)
.disposed(by: disposeBag)
//reactor์ isLoading๊ณผ loadingIndicator๋ฅผ ์ ๋๋ฉ์ด์
ํ ์ง ๋ง์ง ๋ฐ์ธ๋ฉ
reator.state
.map{$0.isLoading}
.distinctUntilChanged()
.bind(to: loadingIndicator.rx.isAnimating)
.disposed(by: disposeBag)
//reactor์ isLoading๊ณผ loadingIndicator๋ฅผ ์จ๊ธธ์ง ๋ง์ง ๋ฐ์ธ๋ฉ
reator.state
.map{!$0.isLoading}
.distinctUntilChanged()
.bind(to: loadingIndicator.rx.isHidden)
.disposed(by: disposeBag)
bind(reator:) ๋ฉ์๋์ ๋งจ ์ฒ์ ๋ง๋ค์ด์ค counterViewReactor๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฃ๊ณ ์คํ์์ผ์ค๋๋ค.
override func viewDidLoad() {
super.viewDidLoad()
bind(reator: counterViewReactor)
}
์คํํ๋ฉด
์๋์ ๊ฐ์ด ๋ฒํผ์ด ๋๋ฆฌ๋ ์ก์
์ ๋ฐ๋ผ์ ๊ฐ์ด ๋ณํํ๊ฒ ๋๊ณ
๋ณํํ ๊ฐ์ ๋ ์ด๋ธ๊ณผ ๋ก๋ฉ์ธ๋์ผ์ดํฐ์ ์ ์ฉ๋๋ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค.
Source Code
์ ์ฒด ์์ค ์ฝ๋๋ ์๋ ๊นํ์ผ๋ก ๊ฐ์ ์ฐธ๊ณ ํด์ฃผ์ธ์!
'๐ iOS > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Design Pattern] RIBs๋?(feat. Uber) (0) | 2021.08.18 |
---|---|
[Design Pattern] MVP ํจํด์ด๋? (0) | 2021.08.11 |
[Design Pattern] Apple์ MVC ํจํด์ด๋? (0) | 2021.06.03 |
[Design Pattern] MVCํจํด์ด๋? (Model-View-Controller) (0) | 2021.06.03 |
[iOS ๋ฉด์ ์ง๋ฌธ] ์ฑ๊ธํค ํจํด์ด๋? (Swift Singleton Pattern) (0) | 2020.12.11 |
๋๊ธ