RxSwift에서는 새로운 Observable을 생성한다거나, 방출되는 요소를 필터링, 혹은 여러 Observable을 하나로 합친다거나 하는 다양한 메소드들이 있다.
그리고 이런것들을 연산자(Operator)라고 부른다. (연산자는 새로운 Observable을 return한다.)
연산자는 보통 subscribe 앞에 추가한다.
즉 구독자(observer)에게 전달되기 전까지 전달 되어야 할 데이터를 원하는 만큼 가공한 후에 전달되게끔 의도할 수 있다.
Create Operators
just
먼저 just 연산자에 대해서 알아보겠다.
just 연산자는 하나의 항목을 방출하는 Observable을 생성한다. (파라미터로 하나의 요소를 받아서 Observable을 return)
let element = "StringElement"
Observable.just(element)
.subscribe { event in print(event) }
.disposed(by: disposeBag)
// next(StringElement)
// completed
Observable.just(["1", "2", "3"])
.subscribe { event in print(event) }
.disposed(by: disposeBag)
// next(["1", "2", "3"]) - just 연산자 이벤트의 타입이 배열이라면 배열을 그대로 전달한다.
// completed
of
2개 이상의 요소를 방출하는 Observable을 만들고 싶을때 of 연산자를 사용하면 여러개의 값을 전달할 수 있다.
Observable.of("1", "2", "3")
.subscribe {
print($0)
}
.disposed(by: disposeBag)
// next(1), next(2), next(3)
// completed
from
배열의 저장된 요소를 하나씩 순서대로 방출하고 싶을때는 from이라는 연산자를 사용한다.
Observable.from(["1", "2", "3"])
.subscribe {
print($0)
}
.disposed(by: disposeBag)
// next(1), next(2), next(3)
// completed
create
위 3개의 연산자는
1. 파라미터로 전달된 요소를 방출하는 Observable을 생성한 후에
2. 모든 요소를 방출하고
3. completed 이벤트를 전달한 후에 종료 된다.
그리하여 'Observable의 기본동작을 바꿀수 없다'라는 제한이 있다.
Observable이 동작하는 방식을 직접 구현하고 싶을때는 create 연산자를 사용한다.
// observer를 파라미터로 받아서 Disposable을 return 하는 클로저를 전달한다. ( (observer) -> (Disposable) )
Observable<String>.create { (observer) -> Disposable in
guard let url = URL(string: "https://www.apple.com") else {
observer.onError(MyError.error) // Error 발생시 구독자에게 Error 이벤트 전달
return Disposables.create() // 모든 리소스 정리, Observable 종료
}
guard let html = try? String(contentsOf: url) else {
observer.onError(MyError.error) // Error 발생시 구독자에게 Error 이벤트 전달
return Disposables.create() // 모든 리소스 정리, Observable 종료
}
observer.onNext(html) // 정상적으로 문자열을 가지고 왔을때 onNext를 통하여 구독자에게 문자열 방출
observer.onCompleted() // 구독자에게 onCompleted 이벤트 전달 (onCompleted 이벤트가 발생했다면 더이상 Next 이벤트는 전달되지 않는다.)
return Disposables.create() // 모든 리소스 정리, Observable 종료
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
만약 존재하지 않는 URL을 사용했을때, 혹은 해당 URL을 String으로 컨버팅을 시도했을때 Error가 발생했다면
Observable은 guard 에 걸려 Error 이벤트를 전달하고 Observable의 모든 리소스를 정리한후 종료된다.
Filtering Operators
elementAt
특정 인덱스에 위치한 요소를 제한적으로 방출하는 방법에 있어 elementAt 연사자를 사용한다.
인덱스를 파라미터로 받아서 Observable을 return 하게되는데
인덱스의 해당하는 하나의 요소를 next 이벤트로 방출한 후에 complete 이벤트를 전달하는 연산자이다.
let elements = ["0번째", "1번째", "2번째", "3번째", "4번째"]
Observable.from(elements)
.elementAt(1) // 인덱스 1에 해당하는 요소를 Next 이벤트로 전달하게 된다.
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(1번째)
// completed
filter
Obseravable이 방출 하는 요소에서 필터링을 거친 후에 구독자에게 이벤트를 전달하고자 할때 filter 연산자를 사용한다.
let elements = [1, 2, 3, 4, 5, 6]
Observable.from(elements)
.filter {
$0.isMultiple(of: 2) // 짝수라는 조건의 필터를 사용 ( true를 return 해야 조건에 성립, 구독자에게는 짝수의 요소만이 전달된다. )
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
// next(2)
// next(4)
// next(6)
// completed
skip
특정 요소를 무시하고자 할때 skip 연산자를 사용한다.
skip 연산자는 정수를 파라미터로 받는다.
그리고 Obseravable이 방출하는 요소중에서 지정된 수만큼 무시한 다음에
이후에 방출되는 요소만 구독자에게 전달된다.
let elements = [1, 2, 3, 4, 5, 6]
Observable.from(elements)
.skip(3) // elements 배열의 앞 3개의 요소들은 skip된다.
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(4)
// next(5)
// next(6)
// completed
skipWhile
skipWhile 연산자는 클로저를 파라미터로 받는다.
해당 클로저에서 정의 해놓은 조건의 결과가 true를 return하는 동안 방출되는 요소를 무시하게 된다.
클로저에서 false를 return 하게되면 그때부터 요소를 방출하기 시작하게 되며,
skipWhile 연산자의 조건에 대해서 더이상 판단하지 않고 계속 방출하게 된다.
let elements = [1, 2, 3, 4, 5, 6]
Observable.from(elements)
.skipWhile {
!$0.isMultiple(of: 2) // 방출하는 요소가 홀수일때만 이벤트를 전달하는 조건 ( 2 부터는 짝수이므로 더이상 skip 하지 않으며 요소를 방출하기 시작하게 된다. )
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(2)
// next(3)
// next(4)
// next(5)
// next(6)
// completed
take
해당 숫자 만큼만 요소를 방출하고자 할때 정수를 파라미터로 받는 take 연산자를 사용한다.
let elements = [1, 2, 3, 4, 5]
Observable.from(elements)
.take(3) // 처음 3개의 요소만 방출하고 나머지 요소는 무시한다.
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(1)
// next(2)
// next(3)
// completed
takeWhile
takeWhile 연산자는 클로저를 파라미터로 받는다.
해당 클로저에서 정의 해놓은 조건의 결과가 true를 return하면 구독자에게 요소를 방출한다.
클로저에서 false를 return 하게되면 더이상 요소를 방출하지 않게 되며
takeWhile 연산자의 조건에 대해서 더이상 판단하지 않고 더이상 요소를 방출하지 않는다.
let elements = [1, 2, 3, 4, 5]
Observable.from(elements)
.takeWhile {
!$0.isMultiple(of: 2) // 홀수일때 요소를 방출하고 짝수 요소가 나왔을때는 false를 return 하여 더이상 요소를 방출하지 않게 된다.
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(1)
// completed
single
Observable에서 첫번째 요소만 방출 한다거나, 어떠한 조건과 일치하는 첫번째 요소만 방출한다.
오직 하나의 요소만 방출을 허용하고 두개 이상의 요소가 방출 되는 경우 Error가 발생한다.
let elements = [1, 2, 3, 4, 5]
Observable.just(1)
.single() // 하나의 요소가 방출됨으로써 Next 이벤트 후에 complete 이벤트 전달
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(1)
// completed
Observable.from(elements)
.single() // 시퀀스에 하나 이상의 요소가 포함 되어 있어 Next 이벤트 후에 Error 발생 ( 요소가 없을때, 0개일때에도 동일 )
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(1)
// error(Sequence contains more than one element.)
Observable.from(elements)
.single {
$0 == 3 // 요소가 3인경우에만 방출하도록 single 연산자의 조건을 추가할 수 있다. ( elements 배열의 요소중 3인 경우는 단한번이므로 정상적으로 처리된다. )
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
.
.
.
// next(3)
// completed
distinctUntilChanged
동일한 항목을 연속적으로 방출하지 않도록 필러링 하고자 할때 distinctUntilChanged 연산자를 사용한다.
이 연산자는 Observable에서 전달되는 두개의 요소를 순서대로 비교한 다음에 이전 요소와 동일 하다면 방출하지 않도록 한다.
let elements = [1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3]
Observable.from(elements)
.distinctUntilChanged() // elements 배열에서 중복 요소를 생략하게 된다. ( 바로 이전 요소와 비교를 하기때문에 연속적인 경우에만 해당한다. )
.subscribe {
print($0)
}
.disposed(by: disposeBag)
// next(1)
// next(2)
// next(3)
// next(1)
// next(2)
// next(3)
// completed
debounce
짧은 시간동안 반복적으로 방출되는 이벤트를 제어하고자 할때 debounce 연산자와 thorottle 연산자를 사용한다.
debounce 연산자를 먼저 보자면 두개의 파라미터를 받는다.
첫번째 파라미터에는 연산자가 Next 이벤트를 방출할지 결정하는 조건으로 사용되는 시간을 전달하고, 두번째 파라미터에는 타이머를 실행할 scheduler를 전달한다.
observer가 Next 이벤트를 방출한 다음 지정된 시간동안 다른 Next 이벤트를 방출하지 않는다면 해당 시점에 가장 마지막으로 방출된 Next 이벤트를 구독자에게 전달하고
지정된 시간 이내에 또다른 Next 이벤트를 방출했다면 Timer를 초기화 하며 다시 지정된 시간동안 대기한다.
해당시간 이내에 다른 이벤트가 방출되지 않는다면 마지막 이벤트를 방출하고, 이벤트가 방출된다면 타이머를 다시 초기화한다.
(ex. 예를 들어 textField의 text가 변경될때마다 이벤트를 발생시킨다고 가정했을때 이벤트가 발생하고 1초라는 시간내에 textField.text의 변경이 또다시 발생한다면 debounce 연산자가 진행하는 Timer는 초기화되어지고 다시 1초라는 시간을 counting하여 textField.text의 변경이 있는지 대기하다가 다른 이벤트가 1초라는 시간내에 발생하지 않았을때 그때 비로소 구독자에게 Next 이벤트를 전달하게 된다. )
private let publishRelay = PublishRelay<String>()
// textField의 delegate 메소드인 textDidChange가 호출될때 publishRelay에 이벤트를 accept 한다.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
publishRelay
.accept(searchText)
}
viewDidLoad {
.
.
.
// textField의 이벤트가 발생하고 해당 text를 가지고 있는 이벤트가 publishRelay에 전달되면
// 구독자에게 Next 이벤트를 전달하게 될껀데 그전에 debounce 연산자를 거치게 된다.
publishRelay
.debounce(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { (text) in
print(text) // 지정된 시간동안 새로운 이벤트가 방출되지 않으면 가장 마지막에 방출된 이벤트를 구독자에게 전달한다.
})
.disposed(by: bag)
.
.
.
}
지정된 주기동안 하나의 이벤트만 구독자에게 전달하고자 할때 throttle 연산자를 사용한다.
throttle 연산자는 3개의 파라미터를 받는다
첫번째 파라미터에는 반복주기를 전달하고 세번째 파라미터에는 scheduler를 전달한다.
두번째 파라미터는 기본값이 true이고 항상 지정된 주기마다 하나씩 이벤트를 전달하며,
false일때는 반복주기가 경과한 다음 가장 먼저 방출되는 이벤트를 구독자에게 전달한다.
넥스트 이벤트를 지정된 주기마다 하나씩 구독자에게 전달한다.
(ex. 예를 들어 사용자가 button을 터치할때마다 이벤트를 발생시킨다고 가정했을때 첫 이벤트가 발생하고 바로 구독자에게 전달된 후에 1초라는 시간내에 button의 터치 이벤트가 추가적으로 발생할 수 있는데 모든 이벤트를 터치할때마다 수행하는게 아니고 1초라는 시간내에 마지막으로 입력되었던 터치 이벤트만 구족자에게 전달 하고자 할때 사용한다.)
즉
처음 이벤트1 ( 바로 구독자에게 전달 ) ->
처음 이벤트로부터 0.3초 경과 ->
이벤트2 ->
처음 이벤트로부터 0.7초 경과 ->
이벤트3 ->
처음 이벤트로부터 1초 경과
이벤트3를 구독자에게 전달한다.
'RxSwift' 카테고리의 다른 글
[RxSwift] Operators (2) (0) | 2021.02.14 |
---|---|
[RxSwift] Subject & Relay (0) | 2021.02.11 |
[RxSwift] Observables & Observer (0) | 2021.02.10 |