RxSwift 개념잡기
- 목표: 스트림 기반 기본 동작에 대한 이해
개념
- 절차형 프로그래밍의 한계: 복잡도가 올라갈 수록 유지보수하기 어려워 진다.
- 스레드에 따른 결과가 달라진다.
- 그래서 콜백/리스너 개념을 도입
콜백/리스너의 단점
- 예측 불가능한 순서: 이벤트가 도착하는 순서는 Observer를 등록한 순서에 따라 변경될 수 있음
- 첫 번째 이벤트 소실
- 스레드 문제
- 콜백 누수: Observer를 해제하는 것을 잊으면 메모리 누수가 발생
FRP (functional reactive programming)
- 선언적 프로그래밍: 개념적으로 어떤 특성이 필요한지 정의. 순차적으로 어떻게 이루어져야 하는지 나열하는 것 보다 효율적.
- Subject를 오직 정보의 근원으로만 봐야한다. 결코 “이제 여기서 코드가 이 값을 푸시한다”라고 말해서는 안된다. 마음 속에서 그런 생각을 아예 없애버려라.
- FRP를 사용하려면 개념적인 수준에서 생각하기 위해 노력하라. 여러 요소 간 상호작용의 매커니즘이 아니라 그들 사이의 관계 수준에서 머물러라.
참조 투명성
- map에 전달하는 함수가 반드시 참조 투명성(referential transparency)을 보장해야 한다.
- 외부에서 볼 수 있는 변수의 상태를 변경해서는 안된다.
- 함수 호출과 호출 사이에 지속되는 상태를 유지해서는 안된다.
- 짧게 말해, 참수는 반환값을 제외한 외부 효과를 만들어서는 안되며, 함수의 반환값이 외부 상태에 의해 영향 받는 일도 없어야 한다.
참고: 함수형 반응형 프로그래밍
Observable
- hot observable: 생성되자마자 데이터를 흘려보내는 것
- cold observable: subscribe 된 다음 데이터를 흘려보내는 것
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Event<Element> {
case next(Element) // next element of a sequence
case error(Swift.Error) // sequence failed with error
case completed // sequence terminated successfully
}
class Observable<Element> {
func subscribe(_ observer: Observer<Element>) -> Disposable
}
protocol ObserverType {
func on(_ event: Event<Element>)
}
Subject
Observable + Observer
- BehaviorSubject: 구독하면 가장 최근 값 가져오기, 구독 이후 반환 값 가져오기
- PublishSubject: 구독 이후 반환 값 가져오기
- ReplaySubject: 구독 이전 값(버퍼 사이즈에 따라 다름), 구독 이후 값 가져오기
상태 관리
Rx에서는 상태를 관리하기 위한 메서드 3개와 클래스 1개를 제공한다.
scan()
: 지정한 초기값으로부터 상태값 누적withLastestFrom()
: 가장 최근의 observable 값 가져오기combineLatest()
BehaviorSubject
예제
UIButton
https://academy.realm.io/kr/posts/how-to-use-rxswift-with-simple-examples-ios-techtalk/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func setup() {
let reloadButton = UIButton()
reloadButton.rx.tap
.debounce(0.3, scheduler: MainScheduler.instance)
.do(onNext: {
print("reload button clicked")
})
.bind(onNext: reload)
.disposed(by: disposeBag)
}
func reload() {
}
UISearchBar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let searchResults = searchBar.rx.text.orEmpty
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query -> Observable<[Repository]> in
if query.isEmpty {
return .just([])
}
return searchGitHub(query)
.catchErrorJustReturn([])
}
.observeOn(MainScheduler.instance)
searchResults
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) {
(index, repository: Repository, cell) in
cell.textLabel?.text = repository.name
cell.detailTextLabel?.text = repository.url
}
.disposed(by: disposeBag)