[RxSwift] Scan
Scan
연산자는 상태를 다루는데 유용한 연산자입니다. 이번 포스트에서는 Scan
연산자의 동작과 활용에 대해 살펴보도록 하겠습니다.
역할
이전에 방출된 아이템과 새로 방출된 아이템을 결합해 현재 아이템을 생성합니다.
값을 계속 누적시킬 수 있기 때문에 누산기(accumulator)라고 부르기도 합니다. 무슨 뜻인지 이해가 잘 안되신다구요? 괜찮습니다. 예제를 살펴 보면 이해가 되실 겁니다.
예제
scan
이 실행되는 구체적인 과정은 다음과 같습니다.
[코드]
Observable.of(1,2,3,4,5)
.scan(0) { (prevValue, newValue) in
return prevValue + newValue
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
[실행 결과]
1
3
6
10
15
scan
은 2개의 인자를 사용합니다.
- 초기값 :
scan
은 새 아이템이 방출될때마다 이전 값을 사용하는데, 최초 실행시에는 이전 값이 없습니다. 그래서 최초에 사용할 이전 값(마지막 값)을 초기값으로 지정합니다.- closure(prevValue, newValue) :
scan
이 실행 될때마다 이 클로저를 실행해 마지막 값과 현재 방출된 값을 인자로 넣어 사용합니다.
대략 감이 오셨나요? 이제부터가 더 흥미로운 내용입니다.
활용
scan
을 여러가지 상태처리에 활용할 수 있습니다.
1. 버튼의 선택 상태변경
버튼을 누를때마다 버튼의 상태가 selected <-> deselected로 번갈아가며 전환됩니다.
[코드]
myButton.rx.tap
.scan(false) { (lastState, newValue) in
!lastState
}
.bind(to: requestMoreButton.rx.isSelected)
.disposed(by: disposeBag)
2. 카운터
버튼을 누를때마다 값을 누적합니다.
[코드]
myButton.rx.tap
.scan(0) { (lastCount, newValue) in
lastCount + 1
}
.subscribe(onNext: { print("taps: \($0)") })
.disposed(by: disposeBag)
[실행 결과]
taps: 1
taps: 2
taps: 3
taps: 4
taps: 5
3. 텍스트 입력값 종류 제한
TextField
나 TextView
에 특정한 문자형태만 입력할 수 있도록 제한하는데 사용할 수 있습니다.
TextField
에 숫자만 입력 가능하도록 scan
을 사용해 다음과 같이 구현할 수 있습니다.
[코드]
myTextField.rx.text.orEmpty.asObservable()
.scan("") { (oldValue, newValue) in
return newValue.isNumber ? newValue : oldValue
}
.bind(to: myTextField.rx.text)
.disposed(by: disposeBag)
extension String {
var isNumber: Bool {
do {
let regex = try NSRegularExpression(pattern: "^[0-9]+$", options: .caseInsensitive)
if let _ = regex.firstMatch(in: self, options: .reportCompletion, range: NSMakeRange(0, count)) { return true }
} catch { return false }
return false
}
}
TextField
의 text가 변경될때마다 이전 text화 변경된 text를 비교하는데, 만약 새로 입력된 문자가 숫자면 새로운 입력을 TextField
에 적용하고, 숫자가 아니면 새로운 입력을 무시하고 이전값을 유지하게 됩니다.
TextField
에 12ab3을 차례로 입력해 보겠습니다.
[실행 결과]
Event next(1)
text: 1
Event next(12)
text: 12
Event next(12a)
text: 12
Event next(12b)
text: 12
Event next(123)
text: 123
12ab3을 입력했지만 TextField
에는 123만이 입력된 것을 확인할 수 있습니다.
4. 화면상태 처리
화면의 상태를 처리하기 위해 scan
을 사용할 수 있습니다.
화면이 A, B, C 세개가 있고 화면은 A -> B -> C 로 이동하는데 뒤로가기도 할 수 있어서 C -> B -> A로도 이동가능하다고 가정해보죠.
만약 B화면이 보여졌는데 A -> B로 이동해서 보여진 경우와 C -> B로 뒤로가기로 이동해 보여진 B화면을 각각 다르게 처리하기 위해 scan
을 사용할 수있습니다.
[코드]
enum SceneState {
case initial
case sceneA
case sceneB
case sceneC
}
let sceneState: BehaviorRelay<SceneState> = BehaviorRelay(value: .initial)
sceneState.asObservable()
scan(.initial) { (prev, current) in
switch (prev, current) {
case (.sceneA, .sceneB)
// 화면이 A -> B로 이동했을 경우 처리
case (.sceneC, .sceneB)
// 화면이 C -> B로 이동했을 경우 처리
}
}
이상으로 scan
연산자에 대해 살펴봤습니다. 상태 관련해 뭔가를 처리해야 한다면 scan
을 사용하는 것을 먼저 생각해보세요.
이 포스트에서 제공한 예제 말고도 다양하게 응용해서 활용할 수 있습니다. (👨🏻💻지식이 +3늘었다.)
다음 포스트에서 또 만나요~ 🚀😄
[참 고]