본문 바로가기
개발/iOS

Redux(ReSwift)를 iOS 프로젝트에 사용해보자!

by 마동쿠 2018. 5. 27.

회사에서 한 화면에 여러 뷰 컨트롤러가 들어가 있거나 이전 화면(뷰 컨트롤러)으로 데이터를 전송해야 하는 화면이 많은 프로젝트를 진행하게 되었습니다.  보통은 프로토콜을 이용하여 delegate로 데이터전달을 했겠지만 이번에는  데이터를 한곳에 저장하고 다른 화면에서도 그 데이터를 바로 이용할 수 있는 Redux(ReSwift)라는 라이브러리를 이용해 보기로 하였습니다. 아래는 github 주소.


https://github.com/ReSwift/ReSwift/


일단 리덕스는 3가지 큰 원칙을 사용합니다


Store(저장소)는 전체 앱 상태를 단일 데이터 구조 형태로 저장합니다. 이 상태는 조치를 저장소에 지정하여 수정할 수 있습니다. 저장소의 상태가 변경될 때마다 저장소는 모든 관찰자에게 알립니다.


Action(액션)은 상태 변경을 설명하는 선언적 방법입니다. 액션은 코드를 포함하지 않으며, 저장소에서 사용되고 Reducer로 전달됩니다. Reducer는 각 작업에 대해 다른 상태 변경을 구현하여 작업을 처리합니다.


Reducer(리듀서)는 순수한 기능을 제공합니다. 현 작업과 현재 앱 상태를 기반으로 새로운 앱 상태를 만듭니다.



(출처 : ReSwift README.md)



간단하게 말하자면 AppDelegate 또는 전역변수에 메인 저장소를 보관하고 Action에서 상태를 변경하는 함수를 선언하고 Reducer에서 Action에서 선언했던 함수를 구현합니다. Reducer로 상태를 변경하면 Store에서 상태 변화를 감지하고 구독중인 개체들에게 상태변화를 알려줍니다.


여기까지가 기본적인 개념을 설명하였고 예제코드와 설명해보면,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
import ReSwift
 
let mainStore = Store<AppState>(
  reducer: counterReducer,
  state: nil
)
 
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  
  var window: UIWindow?

...
}
cs


AppDelegate에 mainStore를 보관해줍니다.



1
2
3
4
5
6
import ReSwift
 
struct AppState: StateType {
  var counter: Int = 0
}
 
cs


state에 counter라는 변수를 선언합니다. 이 값이 변할때마다 Store에서 변화를 감지하고 구독중인 개체들에게 상태변화를 알려줍니다.



1
2
3
4
5
6
import ReSwift
 
// all of the actions that can be applied to the state
struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}
 
cs


Action의 선언.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import ReSwift
 
func counterReducer(action: Action, state: AppState?) -> AppState {
    // if no state has been provided, create the default state
    var state = state ?? AppState()
    
    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
 
    return state
}
 
cs


선언한 Action에 대한 리듀서의 구현, state의 counter값을 1 증가시키거나 감소시키는 코드입니다.



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
32
33
34
 
import UIKit
import ReSwift
 
class ViewController: UIViewController, StoreSubscriber {
    typealias StoreSubscriberStateType = AppState
 
    
    @IBOutlet weak var counterLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // subscribe to state changes
        mainStore.subscribe(self)
    }
    
    func newState(state: AppState) {
        // when the state changes, the UI is updated to reflect the current state
        counterLabel.text = "\(mainStore.state.counter)"
    }
    
    // when either button is tapped, an action is dispatched to the store
    // in order to update the application state
    @IBAction func downTouch(_ sender: AnyObject) {
        mainStore.dispatch(CounterActionDecrease());
    }
    @IBAction func upTouch(_ sender: AnyObject) {
        mainStore.dispatch(CounterActionIncrease());
    }
 
}
 
 
cs


마지막으로 저장소를 구독하고 상태변화가 일어날 때 마다 counterLabel의 텍스트를 변경하는 ViewController의 예제 입니다.


StoreSubscriber를 상속받고, mainStore.subscribe(self)를 한 모든 뷰 컨트롤러 안의 newState(state: AppState) 에서 상태 변화를 받고 뷰에 적용하거나 다양하게 사용할 수 있습니다. 그러므로 프로토콜을 이용한 데이터 전송이나 prepareForSegue에서 다음 뷰 컨트롤러에 직접 값을 전달할 필요가 없게 됩니다.


위 예시들은 ReSwift Github 에서 직접 이용해 볼 수 있고, 아래는 Redux에 관한 이해를 도와줄 사이트들의 목록입니다.


http://reswift.github.io/ReSwift/master/getting-started-guide.html


https://github.com/FEDevelopers/tech.description/wiki/%EB%A6%AC%EB%8D%95%EC%8A%A4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4


https://deminoth.github.io/redux/

댓글