일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- RxSwift
- Safari Inspector
- ReactorKit UnitTest
- firebase
- 시험에자주나오는것만
- alamofire
- ARC
- 앱의생명주기
- hackerrank
- Swift디자인패턴
- IOS
- unittest
- SWIFT
- UIKit
- 카카오맵클론
- Swift코딩테스트
- 반응형프레임워크
- Bubble Search
- firestore
- Di
- TDD
- HackersRank
- ios면접
- iOS앱 디버깅
- 프로그래머스
- mrc
- 코딩테스트입문
- AutoLayout
- RC
- algorithm
- Today
- Total
샘성의 iOS 개발 일지
ReactorKit을 MVVM에 넣어보기 본문
1. ReactorKit이란?
단방향 데이터 흐름을 가진 반응형 앱에 적합한 프레임워크.
기본적으로 RxSwift를 활용하고 있고 개인적으로 더 아키텍처가 통일된 MVVM(?)이라는 느낌을 준다.
2. GitHub :
GitHub - ReactorKit/ReactorKit: A library for reactive and unidirectional Swift applications
A library for reactive and unidirectional Swift applications - GitHub - ReactorKit/ReactorKit: A library for reactive and unidirectional Swift applications
github.com
3. ReactorKit의 구조
1. View :
말 그대로 View. UI를 담당하고 사용자와 상호작용의 역할을 수행.
Reactor의 State(상태)를 구독하고 있으며, State에 따라 UI를 변경함.
2. Reactor
View로부터 일어나는 액션을 받아 처리하는 역할을 함 (마치 ViewModel)
View의 Action을 미리 정의하고, view로부터 받은 action을 처리 후 다시 View에게 상태(State)를 전달함.
3. State :
View의 액션이 일어나면, 변경될 수 있는 상태를 구조체 형태로 나타낸 것.
View는 이 State를 구독하고 State에 반응하여 UI를 변경한다.
4. Action :
View에서 사용자와 상호작용에서 일어나는 액션을 Enum 타입으로 정의한 것. (Input)
4. ReactorKit의 흐름
- View에서 액션이 일어남
- View에서 일어난 액션을 Reactor가 미리 정의해놓은 액션으로 받음
- mutate()에 해당 Action에 대해 어떤 작업(Mutation)을 처리할지 전달
- reduce()에서 작업 수행 + 수행된 작업에 대한 결과(State)를 View에게 전달
- State를 구독하고 있던 View의 UI가 업데이트 됨
5. ReactorKit을 적용한 MVVM 코드 (버튼으로 숫자 + - 하기)
* ReactorKit을 적용한 ViewModel
import Foundation
import ReactorKit
final class TransactionViewModel: ViewModel, Reactor {
let initialState: State
var account: BankAccount
private var disposeBag = DisposeBag()
// Input
/// view로부터 받는 action 정의
enum Action {
case deposit(Int)
case withdraw(Int)
}
/// Action에 대한 작업 단위 정의
enum Mutation {
case increaseBalance(Int)
case decreaseBalance(Int)
}
// Output
/// 현재 상태, view는 State를 구독하여 UI를 업데이트 함
struct State {
let currentBalance: Int
}
// MARK: - Initializer
init(viewModel: ViewModel) {
self.account = viewModel.account
self.initialState = State(currentBalance: viewModel.account.balance)
}
// MARK: - Transform
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .deposit(let amount):
print("+\(amount)")
return Observable.create { emitter in
emitter.onNext(Mutation.increaseBalance(amount))
return Disposables.create()
}
case .withdraw(let amount):
print("-\(amount)")
return Observable.create { emitter in
emitter.onNext(Mutation.decreaseBalance(amount))
return Disposables.create()
}
}
}
func reduce(state: State, mutation: Mutation) -> State {
switch mutation {
case .increaseBalance(let int):
return State(currentBalance: state.currentBalance + int)
case .decreaseBalance(let int):
return State(currentBalance: state.currentBalance - int)
}
}
}
* ViewController
import UIKit
import SnapKit
import Then
import RxCocoa
import ReactorKit
final class TransactionViewController: UIViewController, View {
private var reactor: TransactionViewModel
var disposeBag: RxSwift.DisposeBag
// MARK: - Components
private let balanceView = BalanceLabelView()
private let amonutTextField = UITextField().then {
$0.borderStyle = .roundedRect
$0.layer.borderWidth = 0.5
$0.layer.borderColor = UIColor.gray.cgColor
$0.layer.cornerRadius = 8
$0.textAlignment = .center
$0.keyboardType = .numberPad
$0.font = UIFont.systemFont(ofSize: 22, weight: .medium)
$0.placeholder = "Type price"
}
private let depositButton = TransactionButton(action: .deposit(0))
private let withdrawButton = TransactionButton(action: .withdraw(0))
// MARK: - Lifecycle
override func loadView() {
self.view = balanceView
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setAutolayout()
bind(reactor: reactor)
}
init(viewModel: ViewModel) {
self.reactor = TransactionViewModel(viewModel: viewModel)
self.disposeBag = DisposeBag()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Bind
func bind(reactor: TransactionViewModel) {
bindButtonAction(reactor)
bindState(reactor)
}
// 버튼에 대한 액션 reactor에 전달
private func bindButtonAction(_ reactor: TransactionViewModel) {
depositButton.rx.tap
.filter({ [weak self] in
self!.amonutTextField.text!.count > 0
})
.map({ [weak self] in
self!.amonutTextField.text!
})
.map({ Reactor.Action.deposit(Int($0)!) })
.bind(to: reactor.action)
.disposed(by: disposeBag)
withdrawButton.rx.tap
.filter({ [weak self] in
self!.amonutTextField.text!.count > 0
})
.map({ [weak self] in
self!.amonutTextField.text!
})
.map({ Reactor.Action.withdraw(Int($0)!) })
.bind(to: reactor.action)
.disposed(by: disposeBag)
}
// UI 업데이트
private func bindState(_ reactor: TransactionViewModel) {
reactor.state
.map({ $0.currentBalance })
.map({ "\($0)"})
.bind(to: balanceView.balanceLabel.rx.text)
.disposed(by: disposeBag)
}
...
}
6. 결과
'iOS > ReactiveX' 카테고리의 다른 글
ReactorKit Unit Test하기 (0) | 2023.07.04 |
---|---|
[XCTest] Unit Test작성하기 (feat. RxSwift) (0) | 2023.07.01 |
네이버 검색 API 사용하기 (feat. RxSwift, Alamofire) (0) | 2023.06.23 |