ReSwift는 Swift의 단방향 데이터 흐름 아키텍처를 Redux와 유사하게 구현한 것으로 앱 구성 요소의 세 가지 중요한 문제를 분리하는 데 도움을 준다.
Redux?
리덕스(Redux)는 오픈 소스 자바스크립트 라이브러리의 일종으로, state를 이용한 웹 사이트 혹은 앱의 상태 관리를 목적으로 사용한다.
증가 및 감소할 수 있는 카운터를 유지하는 매우 간단한 앱이 있다고 할 때, 다음과 같이 앱 상태를 정의할 수 있다:
struct AppState {
var counter = 0
}
이 예제와 같이 간단한 Action의 경우 카운터 증가 및 감소에 대한 두 가지 Action을 다음과 같이 빈 구조체로 정의할 수 있다:
Getting Started Guide에서 더 복잡한 Action을 구성하는 방법에 대해 배울 수 있다.
struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}
다음과 같이 switch문을 사용함으로써 Reducer는 다양한 Action type에 응답할 수 있다:
예측 가능한(predictable) 앱 상태를 갖기 위해서 중요한 것.
1. Reducer is always free of side effects.
2. 현재 앱의 상태와 Action을 받아야 한다.
3. 새로운 앱 상태를 return 해야 한다.
func counterReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState()
switch action {
case _ as CounterActionIncrease:
state.counter += 1
case _ as CounterActionDecrease:
state.counter -= 1
default:
break
}
return state
}
상태를 유지하고 Reducer에 Action을 위임하려면 Store가 필요한데, 이 예제에서는 AppDelegate 파일에 mainStore라는 전역 상수를 만들었다.
let mainStore = Store<AppState>(
reducer: counterReducer,
state: nil
)
class AppDelegate: UIResponder, UIApplicationDelegate {
[...]
}
마지막으로, ViewController(or view layer)는 앱 상태를 변경할 때마다 Store 업데이트 및 방출(emitting) Action을 구독함으로써 이 시스템에 연결되어야 한다.
class CounterViewController: UIViewController, StoreSubscriber {
@IBOutlet var counterLabel: UILabel!
override func viewWillAppear(_ animated: Bool) {
mainStore.subscribe(self)
}
override func viewWillDisappear(_ animated: Bool) {
mainStore.unsubscribe(self)
}
func newState(state: AppState) {
counterLabel.text = "\(state.counter)"
}
@IBAction func increaseButtonTapped(_ sender: UIButton) {
mainStore.dispatch(
CounterActionIncrease()
)
}
@IBAction func decreaseButtonTapped(_ sender: UIButton) {
mainStore.dispatch(
CounterActionDecrease()
)
}
}
newState
메서드는 새 앱 상태를 사용할 수 있을 때마다 Store에서 호출되고, 이 메서드에서 최신 앱 상태가 반영되도록 view를 조정해야 한다.이 예제는 ReSwift 기능의 일부만 보여주는 아주 기본적인 예제이고, 이 아키텍처로 전체 앱을 빌드하는 방법을 알아보려면 Getting Started Guide를 참고하면 된다.
증가 및 감소할 수 있는 카운터 예제의 완전한 구현은 CounterExample 프로젝트를 참고하면 된다.
struct MySubState: Equatable {
// 전체 앱 상태에서 파생되어 결합된 하위 상태
init(state: AppState) {
// 필요한 하위 상태를 여기에서 계산(Compute)한다.
}
}
store.subscribe(self) { $0.select(MySubState.init) }
func newState(state: MySubState) {
// Profit!
}
ReSwift recommend reading the brilliant redux documentation.