ReactorKit is a framework for a reactive and unidirectional Swift application architecture.
ReactorKit은 reactive 및 단방향 Swift 애플리케이션 아키텍처를 위한 프레임워크입니다.
ReactorKit을 말로만 많이 들었지 이번 기회에 README.md를 보면서 간단하게 github profile 이미지를 불러오는 예제를 만들어보고 이해해봤습니다.
install은 Podfile, Package.swift 편하신대로 사용하시면 됩니다. 참고로 Carthage는 공식적으로 지원하지는 않음
https://github.com/ReactorKit/ReactorKit
실제로 사용해보면서 느낀점은 익숙해지기만 한다면 불필요한 타이핑 없이 테스트 가능하게 확장이 쉽게 가능하다는 느낌을 받았습니다.
class ViewController: UIViewController, View { // 프로토콜 채택
var disposeBag = DisposeBag()
func bind(reactor: MainReactor) {
}
}
StoryboardView
도 지원합니다. 유의할 점은 View(viewDidLoad)가 로드되고 StoryboardView가 바인딩 한다는 점class MyViewController: UIViewController, StoryboardView { // 프로토콜 채택
func bind(reactor: MyViewReactor) {
// this is called after the view is loaded (viewDidLoad)
}
}
bind(reactor:)
가 호출된다. bind(reactor:)
를 구현하여 작업 스트림과 상태 스트림의 바인딩을 정의Reactor
프로토콜을 채택class MainReactor: Reactor {
let network = Network()
// 사용자 작업을 나타냄
enum Action {
case touchButton(index: Int)
}
// 상태 변경을 나타냄
enum Mutation {
case setImage(image: UIImage?)
}
// 현재 View 상태를 나타냄
struct State {
var image: UIImage?
}
let initialState: State
init() {
self.initialState = State()
}
}
Action
, Mutation
및 State
의 세 가지 type과 initialState 속성을 정의해야 한다mutate()
mutate()
는 Action
을 수신하고 Observable
을 생성합니다. 비동기 작업이나 API 호출과 같은 모든 side effects은 이 메서드에서 수행됩니다.let network = Network()
private let URLs = ["https://avatars.githubusercontent.com/u/62657991?v=4"]
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .touchButton(let index):
return Observable.just(URLs[index])
.flatMap({ self.network.load(url: $0) })
.map({ Mutation.setImage(image: $0) })
}
}
reduce()
reduce()
는 이전 State
와 Mutation
에서 새로운 State
를 생성합니다. State
를 반환해야 합니다. 이 함수에서 어떠한 side effects를 일으키면 안됩니다func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case .setImage(let image):
state.image = image
}
return state
}
transform()
transform 함수들은 다음과 같다
func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>
transform(mutation:)
은 전역 이벤트 스트림을 돌연변이 스트림에 결합하는 데 가장 좋은 위치입니다. 디버깅 목적으로도 사용할 수 있습니다.func transform(action: Observable<Action>) -> Observable<Action> {
return action.debug("action") // Use RxSwift's debug() operator
}
여기까지가 기본적인 내용! 전체 코드는 다음과 같습니다
//SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let vc = ViewController()
vc.view.backgroundColor = .white
window.rootViewController = vc
vc.reactor = MainReactor()
self.window = window
window.makeKeyAndVisible()
}
}
//ViewController
class ViewController: UIViewController, View {
var disposeBag = DisposeBag()
let loadButton: UIButton = {
let button = UIButton()
button.setTitle("load Image", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
return button
}()
lazy var centerImageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 350, height: 350))
imageView.image = UIImage()
imageView.center = view.center
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
makeUI()
loadButton.addAction(UIAction(handler: { _ in
print("Hello")
}), for: .touchUpInside)
}
func makeUI() {
view.addSubview(centerImageView)
view.addSubview(loadButton)
loadButton.translatesAutoresizingMaskIntoConstraints = false
loadButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
func bind(reactor: MainReactor) {
loadButton.rx.tap
.map({ Reactor.Action.touchButton(index: 0) })
.bind(to: reactor.action)
.disposed(by: disposeBag)
reactor.state
.map({ $0.image })
.bind(to: centerImageView.rx.image)
.disposed(by: disposeBag)
}
}
//MainReactor
class MainReactor: Reactor {
let network = Network()
private let URLs = ["https://avatars.githubusercontent.com/u/62657991?v=4"]
enum Action {
case touchButton(index: Int)
}
enum Mutation {
case setImage(image: UIImage?)
}
struct State {
var image: UIImage?
}
let initialState: State
init() {
self.initialState = State()
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .touchButton(let index):
return Observable.just(URLs[index])
.flatMap({ self.network.load(url: $0) })
.map({ Mutation.setImage(image: $0) })
}
}
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case .setImage(let image):
state.image = image
}
return state
}
}
//Network
class Network {
func load(url: String) -> Single<UIImage?> {
let request = URLRequest(url: URL(string: url)!)
return URLSession.shared.rx.response(request: request)
.map({ UIImage(data: $0.data) })
.asSingle()
}
}
간단하게 ReactorKit을 알아봤는데 Example을 보면서 좀 더 심화된 내용을 알아야 할 필요가 있다고 생각해 다음 포스트에 더 이어서 작성해보도록 합니다