Observables
๐ Observables โ RxSwift์ ํต์ฌ ์คํธ๋ฆผ
โEverything is a sequence over time.โ โ Rx ๋์์ธ ์ฒ ํ
์ด ๋ฌธ์์์๋ Observable
์ด ๋ฌด์์ธ์ง, ์ด๋ป๊ฒ ์์ฑํ๊ณ (Factory), ๊ตฌ๋
ํ๋ฉฐ(Subscribe), ๊ด๋ฆฌ(Dispose)ํ๋์ง ๋จ๊ณ๋ณ๋ก ์ดํด๋ด
๋๋ค.
1๏ธโฃ Observable ์๋ช
์ฃผ๊ธฐ
Observable<Element>
๋ ์ด๋ฒคํธ(Event) ์คํธ๋ฆผ์
๋๋ค. ๊ฐ ์คํธ๋ฆผ์ ์ต๋ ๋ค ์ข
๋ฅ์ ์๊ทธ๋์ ์์๋๋ก ๋ฐฉ์ถํฉ๋๋ค.
next
์์ ๊ฐ ์ ๋ฌ
onNext(T)
โ
error
์ค๋ฅ ๋ฐ์
onError(Error)
โ ์คํธ๋ฆผ ์ข ๋ฃ
completed
์ ์ ์ข ๋ฃ
onCompleted()
โ ์คํธ๋ฆผ ์ข ๋ฃ
disposed
๋ฆฌ์์ค ํด์
onDisposed()
โ
next
๋ ์ฌ๋ฌ ๋ฒ ๋ฐ์ํ ์ ์์ง๋ง,error
๋๋completed
๋ ๋จ ํ ๋ฒ๋ง ๋ฐ์ํ๋ฉฐ ๋ ์ค ํ๋๋ง ๋ฐฉ์ถ๋ฉ๋๋ค.
2๏ธโฃ Observables ๋ง๋ค๊ธฐ (Factory Methods)
just(_:)
ํ๋์ ์์๋ง ๋ฐฉ์ถ ํ ์๋ฃ
Observable.just("๐")
of(_:)
์ฌ๋ฌ ์์ ์์ฐจ ๋ฐฉ์ถ
Observable.of(1,2,3)
from(_:)
๋ฐฐ์ดยท์ํ์ค โ ์คํธ๋ฆผ
Observable.from(["A","B"])
create(_:)
์ปค์คํ ์ด๋ฒคํธ ์ ์
Observable<String>.create { observer in ... }
deferred(_:)
๊ตฌ๋ ์์ ๋ง๋ค ์ ์คํธ๋ฆผ ์์ฑ
Observable.deferred { Bool.random() ? .just("โ
") : .error(err) }
interval
์ผ์ ์ฃผ๊ธฐ ์ซ์ ๋ฐฉ์ถ
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
timer
์ง์ฐ ํ 1ํ ๋๋ ์ฃผ๊ธฐ ๋ฐฉ์ถ
Observable<Int>.timer(.seconds(3), period: .seconds(1), scheduler: Main)
// ์: 0.5์ด๋ง๋ค ์นด์ดํธ ์
์คํธ๋ฆผ
let counter = Observable<Int>
.interval(.milliseconds(500), scheduler: MainScheduler.instance)
๐ก Tip: ํ๋ ์ด๊ทธ๋ผ์ด๋์์ PlaygroundPage.current.needsIndefiniteExecution = true
์ค์ ํ ์คํํด ๋ณด์ธ์.
3๏ธโฃ Cold vs Hot Observables
Cold
๊ตฌ๋ ๋ง๋ค ์ ๋ฐ์ดํฐ ์์ฐ
Observable<Int>.range(0, 5)
, URLSession.rx.response
Hot
์ด๋ฏธ ํ๋ฅด๊ณ ์๋ ๊ณต์ ๋ฐ์ดํฐ
PublishSubject
, NotificationCenter.default.rx.notification
// Cold ์์
let range = Observable.range(start: 0, count: 3)
range.subscribe(onNext: { print("๐
ฐ๏ธ", $0) })
range.subscribe(onNext: { print("๐
ฑ๏ธ", $0) }) // ๋ ๊ตฌ๋
์ ๋ชจ๋ 0,1,2 ๋ฐ์
// Hot ์์
let subject = PublishSubject<Int>()
subject.subscribe(onNext: { print("1st ->", $0) })
subject.onNext(1)
subject.subscribe(onNext: { print("2nd ->", $0) }) // ๋ ๋ฒ์งธ๋ 2๋ถํฐ ์์
subject.onNext(2)
4๏ธโฃ ๊ตฌ๋
& DisposeBag
let bag = DisposeBag()
Observable.from(["๐","๐","๐"])
.subscribe(
onNext: { print("fruit:",$0) },
onError: { print("error:",$0) },
onCompleted: { print("completed") },
onDisposed: { print("disposed") }
)
.disposed(by: bag)
DisposeBag: ๊ตฌ๋ (disposable)์ ๋ด์ ์ค์ฝํ ๋จ์๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํฉ๋๋ค. ํด๋์ค๋ณ๋ก
let bag = DisposeBag()
๋ฅผ ๋ณด์ ํ๋ ํจํด์ด ์ผ๋ฐ์ ์ ๋๋ค.
rx.deallocated
์คํธ๋ฆผ์ ํ์ฉํ๋ฉด ๋ทฐ ์๋ฉธ ์ ์๋ ํด์ ํจํด์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
5๏ธโฃ ์ค์ ์์ โ URLSession + JSON ๋์ฝ๋ฉ
struct Post: Decodable { let id: Int; let title: String }
func fetchPosts() -> Observable<[Post]> {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
return URLSession.shared.rx.data(request: URLRequest(url: url))
.map { data in try JSONDecoder().decode([Post].self, from: data) }
}
fetchPosts()
.observe(on: MainScheduler.instance)
.subscribe(onNext: { print($0.first?.title ?? "-") })
.disposed(by: bag)
6๏ธโฃ Cheat Sheet
Factory
just
, of
, from
, create
, deferred
, empty
, never
, error
, interval
, timer
, range
Filtering
filter
, distinctUntilChanged
, take
, skip
, debounce
, throttle
Transform
map
, flatMap
, flatMapLatest
, buffer
, scan
Combining
merge
, concat
, zip
, combineLatest
, withLatestFrom
Error
catchError
, retry
, retryWhen
7๏ธโฃ Mini Quiz
flatMapLatest
์switchMap
(Kotlin Flow) ์ฐจ์ด๋ฅผ ์ค๋ช ํด ๋ณด์ธ์.Cold Observable์์ ๋ ๋ฒ์งธ ๊ตฌ๋ ์ด ์ด์ ๊ฐ์ ์ฌ์์ฐ์ ๋ง์ผ๋ ค๋ฉด ์ด๋ค ์คํผ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ์ ์์๊น์?
DisposeBag
์์ด๋ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ์ง ์๋ ๊ฒฝ์ฐ๋?
Last updated