RxSwift
- 비동기 처리를 쉽게 처리할 수 있게 해주는 라이브러리.
- 강력한 Operator.
https://cocoapods.org/pods/RxSwift
ReactorKit
- 단방향 데이터 흐름을 가진 반응형 앱을 위한 프레임워크.
https://cocoapods.org/pods/ReactorKit
(ReactorKit 기본 개념)
* Action
View에서 나타나는 Action을 Enum으로 정의한 것
예를 들어, 버튼1 클릭됨 / 버튼2 클릭됨 / 테이블 뷰 스크롤 됨 / 화면이 나타남
* Mutation
화면 구현에 있어 필요한 기능을 기본 로직 단위로 분류해 Enum으로 정의한 것
예를 들어, API 호출해서 서버시간 가져오기 / 숫자 더하기 / 글자 변경하기
* State
Mutation에 따라 실제로 값이 변경될 변수들을 struct 형태로 묶어둔 것
- mutate() 함수를 통해 Action -> Mutation 매핑 // 아래 예시 참고
- reduce() 함수를 통해 Mutation -> State 매핑 // 아래 예시 참고
예시 프로젝트
기능 1) PLUS 버튼을 누르면 +1, MINUS 버튼을 누르면 -1 처리
기능 2) 비동기 방식으로 API 호출 후, 결과 처리 (단순히 echo 결과 return 해주는 Postman Echo API 사용)
// View - MainViewController.swift
import UIKit
import RxCocoa
import RxSwift
import ReactorKit
class MainViewController: ViewController {
var disposeBag = DisposeBag()
let reactor = MainViewReactor()
// 기능 1
@IBOutlet var plusBtn: UIButton!
@IBOutlet var minusBtn: UIButton!
@IBOutlet var label: UILabel!
// 기능 2
@IBOutlet var apiLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
bind(reactor: reactor)
reactor.action.onNext(.viewDidLoad("hi", 1234))
}
func bind(reactor: MainViewReactor) {
// set action
plusBtn.rx.tap
.map{ MainViewReactor.Action.plus }
.bind(to: reactor.action)
.disposed(by: disposeBag)
minusBtn.rx.tap
.map{ MainViewReactor.Action.minus }
.bind(to: reactor.action)
.disposed(by: disposeBag)
// detect state changed
reactor.state.map { $0.number }
.distinctUntilChanged()
.map{ "\($0)" }
.subscribe(onNext: { str in
self.label.text = str
})
.disposed(by: disposeBag)
reactor.state
.filter { $0.data1 != "" && $0.data2 != 0 }
.map { "\($0.data1), \($0.data2)" }
.distinctUntilChanged()
.subscribe(onNext: {
self.apiLabel.text = $0
})
.disposed(by: disposeBag)
}
}
// View Model - MainViewReactor.swift
import Foundation
import ReactorKit
import SwiftyJSON
class MainViewReactor: Reactor {
let initialState: State = State(number: 0)
// represent user actions
enum Action {
case viewDidLoad(String, Int)
case plus, minus
}
// represent state changes
enum Mutation {
case setDataFromApi(String, Int)
case setNumberTPlusFMinus(Bool)
}
// represents the current view state
struct State {
var data1: String = ""
var data2: Int = 0
var number: Int = 0
}
// Action -> Mutation
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .viewDidLoad(let data1, let data2):
return Observable.concat([
getDataFromApi(data1: data1, data2: data2)
])
case .plus:
return Observable.concat([
Observable.just(Mutation.setNumberTPlusFMinus(true)),
])
case .minus:
return Observable.concat([
Observable.just(Mutation.setNumberTPlusFMinus(false)),
])
}
}
// Mutation -> State
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case .setDataFromApi(let data1, let data2):
state.data1 = data1
state.data2 = data2
case .setNumberTPlusFMinus(let flag):
if flag { state.number += 1 }
else { state.number -= 1 }
}
return state
}
// MARK: Private Functions
func getDataFromApi(data1: String, data2: Int) -> Observable<Mutation> {
return Observable<Mutation>.create { observer in
ApiRequest.shared.getEchoTest(data1: data1, data2: data2)
.map { $0["args"] }
.subscribe(onNext: {
observer.onNext(.setDataFromApi($0["data1"].stringValue, $0["data2"].intValue))
observer.onCompleted()
})
return Disposables.create()
}
}
}
// Model - ApiRequest.swift
import RxSwift
import SwiftyJSON
import Alamofire
class ApiRequest {
static var shared = ApiRequest()
private init() {}
private let POSTMAN_ECHO_SERVER = "https://postman-echo.com/"
/**
Postman Echo Test Api
*/
func getEchoTest(data1: String, data2: Int) -> Observable<JSON> {
let url = POSTMAN_ECHO_SERVER + "get"
let params: [String: Any] = [
"data1": data1,
"data2": data2,
]
return getJson(url, params: params)
}
// MARK: Private Functions
private func getJson(_ url: String, params: [String: Any]) -> Observable<JSON> {
return Observable.create { observer in
AF.request(url, method: .get, parameters: params).response { response in
switch (response.result) {
case .success(let value):
observer.onNext(JSON(value!))
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
}
return Disposables.create()
}
}
}
참고
'iOS' 카테고리의 다른 글
[iOS] Warning 없는 Clean Code 만들기 (SwiftLint, Pods file) (0) | 2020.10.07 |
---|---|
[iOS] RealmSwift 라이브러리 사용해보기 (0) | 2020.10.06 |
[iOS] Xcode 업로드 오류 해결법 (App Store Connect 업로드 불가) (0) | 2020.09.21 |
[Firebase] Firebase Cloud Messaging (FCM) (0) | 2020.08.16 |
[iOS] 메모리 구조 및 관리(MRC, ARC) (0) | 2020.08.16 |