ย Observable
์ด๋ฒคํธ ๋ฐํ์ ์ฃผ์ฒด์ธ Observable์ ์ด๋ฒคํธ๋ฅผ ์์์ฃผ์ํ๊ณ ์๋ค๊ฐ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ํด๋น ์ด๋ฒคํธ๋ฅผ Observer์๊ฒ ์ ๋ฌํฉ๋๋ค.
Observable์ด ์ด๋ฒคํธ๋ฅผ ํ์ธํ๋ฉด Observer์๊ฒ ๋ฐ๋ก ์ ๋ฌํด์ค์ผํ๊ธฐ ๋๋ฌธ์ Observable๊ณผ Observer๋ ์ฐ๋ฝ์ ํ๊ณ ์๋ ์ํ์ฌ์ผ ํฉ๋๋ค. ์ด ์ํ๋ฅผ โsubscribeโ, โ๊ตฌ๋
ํ๋คโ ๋ผ๊ณ ํฉ๋๋ค.
์ ํ๋ธ์์ ์ข์ํ๋ ์ ํ๋ฒ๋ฅผ ๊ตฌ๋
ํ๋ฉด ํด๋น ์ ํ๋ฒ๊ฐ ์๋ก์ด ์์์ ์ฌ๋ ธ์ ์์ ์๋ฆผ์ ๋ฐ๊ฒ ๋ฉ๋๋ค. ์ด์ ๋น์ทํ๊ฒ Observable์ ๊ตฌ๋
ํ Observer๋ ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ๋ฐ๊ฒ ๋ฉ๋๋ค.
Observable ์์ฒด๋ ํ๋์ ์ฌ๊ฑด์ด ์๋๊ณ , ์ฌ๊ฑด๋ค์ ์์๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ด๋ฒคํธ ๋ฐ์์ ์ฃผ์ฒด์
๋๋ค. ์ฆ, Observable์ Observable sequence, stream์
๋๋ค.
Observable์ด ๋ฐํํ๋ ์ด๋ฒคํธ์ ํํ๋ ์ด 3๊ฐ์ง ์
๋๋ค.
1.
next : ์๋ก์ด ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ๊ตฌ๋
์์๊ฒ ์ ๋ฌ
2.
error : ์๋ฌ ๋ฐ์
3.
completed : ์ ์์ ์ผ๋ก ์ข
๋ฃ
onNext
Observable์ ๊ตฌ๋
ํ๊ณ ์๋ Observer์๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํฉ๋๋ค.
onNext๋ Observable์ Stream์ด ๋๊ธฐ์ง ์์๊ธฐ ๋๋ฌธ์ ์ด๋ฒคํธ๋ฅผ ๊ณ์ ๋ฐฉ์ถ์ํฌ ์ ์์ต๋๋ค.
onCompleted
Observable์ ๊ตฌ๋
ํ๊ณ ์๋ Observer์๊ฒ ํด๋น Observable์ Stream์ด ์ฑ๊ณต์ ์ผ๋ก ๋๋ฌ์์ ์๋ฆฝ๋๋ค.
onCompleted๋ Stream์ ๋๊ณ ์ด๋ฒคํธ๋ฅผ ์ข
๋ฃ์ํฌ ์ ์์ต๋๋ค.
onError
Observable์ ๊ตฌ๋
ํ๊ณ ์๋ Observer์๊ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ ์๋ฆฌ๊ณ Stream์ ์ข
๋ฃ์ํต๋๋ค.
onError๋ ์ด๋ฒคํธ์ ์ค๋ฅ๊ฐ ์์์ ์๊ณ ์ค๊ฐ์ Stream์ ์ข
๋ฃ์ํต๋๋ค.
๊ทธ๋ผ, ์ด๋ฒคํธ ๋ฐํ์ ์ฃผ์ฒด์ธ Observable์ ์ด๋ป๊ฒ ์์ฑ๋๋๊ฑธ๊น์?
ย Create Observable
Observable์ ์์ฑํ๋ Operator๋ฅผ ๋ง๋๋ด
์๋ค.
just
ํ๋์ ํญ๋ชฉ์ ๋ฐฉ์ถํ๋ Observable์ ์์ฑ
Observable.just([1, 2, 3])
.subscribe { event in print(event) }
.disposed(by: disposedBag)
/*
next([1, 2, 3])
completed
*/
Swift
๋ณต์ฌ
from
๋ฐฐ์ด์ ํฌํจ๋ ์์๋ฅผ ํ๋์ฉ ์์๋๋ก ๋ฐฉ์ถํ๋ Observable์ ์์ฑ
Observable.from([1, 2, 3])
.subscribe{ element in print(element) }
.disposed(by: disposedBag)
/*
next(1)
next(2)
next(3)
completed
*/
Swift
๋ณต์ฌ
create
์ง์ ์ ์ธ ์ฝ๋ ๊ตฌํ์ ํตํด Observer ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ Observable์ ์์ฑ
Observable<String>.create { observer in
observer.onNext("A")
observer.onCompleted()
return Disposables.create()
}.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Disposed") }
).disposed(by: disposedBag)
/*
A
Completed
Disposed
*/
Swift
๋ณต์ฌ
deferred
Observer๊ฐ ๊ตฌ๋
ํ๊ธฐ ์ ๊น์ง Observable ์์ฑ์ ์ง์ฐํ๋ค๊ฐ, ๊ตฌ๋
์ด ์์๋๋ฉด Observer ๋ณ๋ก ์๋ก์ด Observable์ ์์ฑ
.subscribe() ํ๋ ์๊ฐ .deferred()๊ฐ ์คํ๋ฉ๋๋ค.
let a = ["a", "a"]
let b = ["b", "b"]
var flag = true
let observable: Observable<String> = Observable.deferred {
flag = !flag
if flag {
return Observable.from(a)
} else {
return Observable.from(b)
}
}
observable
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
a
a
completed
*/
observable
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
b
b
completed
*/
Swift
๋ณต์ฌ
range
์ฐ์๋ ๋ฒ์์ ์ ์๋ฅผ ๋ฐํํ๋ Observable์ ์์ฑ
start๋ถํฐ count ํฌ๊ธฐ ๋งํผ์ ๊ฐ์ ๊ฐ์ง๋ Observable์ ์์ฑํฉ๋๋ค.
Observable.range(start: 1, count: 10)
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
next(1)
next(2)
..
next(10)
*/
Swift
๋ณต์ฌ
generate
์ค์ ํ ์กฐ๊ฑด์ ๋ง๋ ์ ์๋ฅผ ๋ฐํํ๋ Observable์ ์์ฑ
โข
initialState : ์์ ๊ฐ (๊ฐ์ฅ ๋จผ์ ๋ฐฉ์ถ๋๋ ๊ฐ)
โข
condition : ์กฐ๊ฑด์ด true์ผ ๊ฒฝ์ฐ์๋ง ์์๊ฐ ๋ฐฉ์ถ๋จ (false์ผ ๊ฒฝ์ฐ completed)
โข
iterate : ๊ฐ์ ์ฆ๊ฐ์ํค๊ฑฐ๋ ๊ฐ์์ํฌ ๊ฒฝ์ฐ์ ์ฌ์ฉ
Observable.generate(initialState: 0, condition: { $0 <= 10 }, iterate: { $0 + 2 })
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
next(0)
next(2)
next(4)
next(6)
next(8)
next(10)
completed
*/
Swift
๋ณต์ฌ
repeatElement
ํน์ ์์๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ฐฉ์ถํ๋ Observable์ ์์ฑ
Observable.repeatElement("A")
//.take(7) // take Operater๋ฅผ ๋ฃ์ผ๋ฉด 7๊ฐ๋ง ๋ฐฉ์ถํ๊ณ ์ข
๋ฃ
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
next(A)
next(A)
next(A)
...
*/
Swift
๋ณต์ฌ
interval
์ง์ ๋ ์๊ฐ์ ํ ๋ฒ์ฉ ์ด๋ฒคํธ๋ฅผ ๋ฐฉ์ถ
Observable<Int>.interval(.seconds(3), scheduler: MainScheduler.instance)
.take(10)
.subscribe { print($0) }
.disposed(by: disposedBag)
// 3์ด๋ง๋ค 0๋ถํฐ ์ซ์๊ฐ ์ฆ๊ฐํ๊ฒ ๋๋ค.
/*
next(0)
next(1)
...
next(9)
completed
*/
Swift
๋ณต์ฌ
empty
ํญ๋ชฉ์ ๊ฐ์ง์ง ์๋ Observable์ ์์ฑ
empty๋ .completed ์ด๋ฒคํธ๋ง ๋ฐฉ์ถํฉ๋๋ค. ํญ๋ชฉ์ ์์ง๋ง ์ ์์ ์ผ๋ก ์ข
๋ฃ๋ฉ๋๋ค.
Observable<Void>.empty()
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
completed
*/
Swift
๋ณต์ฌ
never
ํญ๋ชฉ์ ๋ฐฉ์ถํ์ง ์๊ณ ์ข
๋ฃํ์ง ์๋ Observable์ ์์ฑ
Observable<Void>.never()
.subscribe { print($0) }
.disposed(by: disposedBag)
// never๋ complete ํธ์ถ๋์ง ์์.
Swift
๋ณต์ฌ
error
ํญ๋ชฉ์ ๋ฐฉ์ถํ์ง ์๊ณ ์ค๋ฅ๋ก ์ข
๋ฃ๋๋ Observable์ ์์ฑ
Observable<Void>.error(NSError(domain: "400", code: 400))
.subscribe { print($0) }
.disposed(by: disposedBag)
/*
error(Error Domain=400 Code=400 "(null)")
*/
Swift
๋ณต์ฌ
ย Subscribe Observable
Observable์ ์์ฑ ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด์ ๋ง๋ค์ด๋ ๊ตฌ๋
์ด ๋์ด ์์ง ์์ผ๋ฉด ์๋ฌด๋ฐ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
๋ด๊ฐ ์ข์ํ๋ ์ ํ๋ฒ๋ฅผ ๊ตฌ๋
ํ์ง ์์ผ๋ฉด ์๋ก์ด ์์์ ๋ํ ์๋ฌด๋ฐ ์๋ฆผ์ ๋ฐ์ ์ ์๋ ๊ฑฐ์ฒ๋ผ์. ํ์ง๋ง, ํด๋น ์ ํ๋ฒ๋ฅผ ๊ตฌ๋
ํ๋ ์๊ฐ๋ถํฐ๋ ์๋ก์ด ์์์ ๋ํ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
๊ตฌ๋
์ โsubscribeโ๋ง ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
๋ง์ฝ, ์ด๋ฐ Observable์ด ์๋ค๊ณ ํด๋ด
์๋ค. Observable์ ๋ฐฐ์ด์์ String ํ์
์ ํญ๋ชฉ์ ํ๋์ฉ ๋นผ์ ์ ๋ฌํด์ค๋๋ค.
let strObservable = Observable.from(["์๋
", "ํ์ธ์", "๋ฐ๊ฐ", "์ต๋๋ค"])
Swift
๋ณต์ฌ
ํ์ง๋ง, ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ ์ถ์ดํ๋ Observer๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๋ฌด๋ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์๊ฐ ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด, ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๊ฒ๋ ํด๋น ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋
ํ๋ ์ฝ๋๋ฅผ ์์ฑํด๋ณผ๊ฒ์.
strObservable.subscribe { print($0) }
Swift
๋ณต์ฌ
์ด๋ ๊ฒ ํ๋ฉด, Observable์ด ๋ฐฉ์ถํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ Observer๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ์ ๋ฌ๋ฉ๋๋ค.
/*
next(์๋
)
next(ํ์ธ์)
next(๋ฐ๊ฐ)
next(์ต๋๋ค)
completed
*/
Swift
๋ณต์ฌ
ํ์ง๋ง, ๊ตฌ๋
ํ๊ณ ๋์ด ์๋๋๋ค.
๊ตฌ๋
ํ๊ณ ๋์ Observable๊ณผ ๊ด๋ จ๋ ๋ฆฌ์์ค๋ฅผ ๊ทธ๋๋ก ๋จ๊ฒจ๋๋ค๋ฉด ๋์ค์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด, ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ ์ ์๋์?
subscribe ์๋ next, error, completed๋ง๊ณ ๋ disposed๋ผ๋ ์ต์
๋ ํ์
ํด๋ก์ ์ธ์๊ฐ ์กด์ฌํฉ๋๋ค.
disposed๋ Observable๊ณผ ๊ด๋ จ๋ ๋ชจ๋ ๋ฆฌ์์ค๊ฐ ์ ๊ฑฐ๋ ๋ค์์ ํธ์ถ๋๋ ํด๋ก์ ์
๋๋ค.
Observable ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ ๋์ ์คํ์ํค๊ณ ์ถ์ ์ฝ๋๋ disposed ๋ถ๋ถ์ ๋ฃ์ ์ ์์ต๋๋ค.
strObservable.subscribe(onNext: {
print($0)
}, onError: {_ in
// Observable error
}, onCompleted: {
// Observable completed
}, onDisposed: {
// ๋ฉ๋ชจ๋ฆฌ ํด์ ์์ ํธ์ถํ๊ณ ์ถ์ ์ฝ๋
})
Swift
๋ณต์ฌ
onError๋ onCompleted๋์ด์ Stream์ด ๋๋๋ฉด onDisposed๊ฐ ๋ถ๋ฆฌ๊ฒ ๋ฉ๋๋ค.
error, completed ์ดํ์ ๋ฆฌ์์ค ๊ด๋ฆฌํด์ค ํ์์์ด ์ ์์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋์ง๋ง ๊ณต์ ๊ฐ์ด๋๋ผ์ธ์ ๋ฐ๋ฅด๋ฉด ์ด๋ฐ ๊ฒฝ์ฐ์๋ ์๋์ ์ผ๋ก ๋ฆฌ์์ค๋ฅผ ํด์ ํด์ค ๊ฒ์ ๊ถ๊ณ ํ๊ณ ์์ต๋๋ค.
์๋์ ์ธ ๋ฆฌ์์ค ํด์ ๋ฅผ ์ํด์ disposed๋ฅผ ์ฌ์ฉํด์ฃผ๋ฉด ๋ฉ๋๋ค.
Observable.from([1, 2, 3])
.subscribe{ element in print(element) }
.disposed(by: disposedBag)
Swift
๋ณต์ฌ
disposed ๋ฉ์๋์์ ์ธ์๋ก ์ฌ์ฉ๋๋ disposedBag์ Disposable ํ์
์ ๋ชจ๋ ๋ชจ์์ ํ๊บผ๋ฒ์ ํด์ ํฉ๋๋ค. ๋ฐ๋ผ์, Observable์ ์์ฑํ๊ณ ๊ตฌ๋
ํ ๋ค์์ ๋ฐ๋ก dispose ์ํค๋ฉด ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ทธ๋ผ, ๊ตฌ๋
ํ๊ณ ๋์ ๋ฐ๋ก dispose๋๋๊ฑด ์๋๊ฐ์?
์๋๋๋ค. disposeBag์ ๋ด๊ฒจ์๋ disposable์ disposeBag์ด ํด์ ๋ ๋ ํจ๊ป ํด์ ๋ฉ๋๋ค.
ย Trait
Trait๋ ์ผ๋ฐ์ ์ธ Observable๋ณด๋ค ์ข์ ๋ฒ์์ Observable์
๋๋ค.
Single
๋จ์ผ ํญ๋ชฉ์ด๋ ์ค๋ฅ ๋ฐฉ์ถ
Single์ Observable์ ํ ํํ์
๋๋ค. ํน์ดํ ์ ์ด ์๋ค๋ฉด ํ ๊ฐ์ง ๊ฐ์ด๋ ์๋ฌ๋ฅผ ๋ฐฉ์ถํ๋ค๋ ์ ์
๋๋ค.
๊ฐ์ด success ๋๋ error ํํ๋ก ๋ฐฉ์ถ๋ฉ๋๋ค.
๋ฐ๋ผ์, โ์ฑ๊ณต์ด๋โ, โ์คํจ๋โ๋ฅผ ๋ฐ์ง๋ ์ผ์ ์ ํฉํ Observable ์
๋๋ค.
func getRepo(_ repo: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
if let error = error {
single(.error(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.error(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}
getRepo("ReactiveX/RxSwift")
.subscribe(onSuccess: { json in
print("JSON: ", json)
}, onError: { error in
print("Error: ", error)
})
.disposed(by: disposeBag)
Swift
๋ณต์ฌ
Observable์ completed ์ด๋ฒคํธ๋ฅผ ๋ฐํํ ์ ์์ง๋ง, Single์ completed ์ด๋ฒคํธ๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค. ์ฆ, Single์ ์ด๋ฒคํธ์ธ success ์์ฒด๊ฐ next, completed ๋ ๊ฐ์ ์ฑ๊ฒฉ์ ๋ค ํฌํจํ๊ณ ์๊ธฐ ๋๋ฌธ์, completed ์ด๋ฒคํธ๊ฐ ๋ฐํ๋ ์ํ์์ ์ด์ ์ next ์ด๋ฒคํธ๊ฐ ๋ค์ด์ค์ง ์์ผ๋ฉด ์๋ฌ๋ฅผ ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค.
case .completed:
if self.seenValue {
self.forwardOn(.completed)
} else {
self.forwardOn(.error(RxError.noElements)) // ์๋ฌ ๋ฐ์
}
self.dispose()
}
Swift
๋ณต์ฌ
Observable์ asSingle()์ด๋ผ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ Observable ํ์
์ Single ํ์
์ผ๋ก ๋ฐ๊ฟ ์ ์์ต๋๋ค. ์ด ์ํฉ์์ ์ด์ ๊ณผ ๋์ผํ๊ฒ completed ์ด๋ฒคํธ๊ฐ ๋ฐํ๋๋ค๋ฉด ๊ทธ ์ด์ ์ next ์ด๋ฒคํธ๊ฐ ๋ค์ด์์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด, ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค.
case .completed:
if let element = self.element {
self.forwardOn(element)
self.forwardOn(.completed)
}
else {
self.forwardOn(.error(RxError.noElements))
}
self.dispose()
}
Swift
๋ณต์ฌ
Single์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด ๋ฐ๋์ Single๋ก ์์ํ๋๋ก ํ๊ณ , Observable๋ก ์์ํ ๊ฒฝ์ฐ์๋ ๋ถํ์ํ ์๋ฌ๋ฅผ ๋ฐํํ ์ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๊ธ์ ์ค๊ฐ์ Single๋ก ํ์
๋ณ๊ฒฝ์ ํ๋ ์ผ์ ์ง์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
Completable
Single๊ณผ ๊ธฐ๋ฅ์ด ์ ์ฌํ์ง๋ง, value๊ฐ์ ๋ฐฉ์ถํ์ง ์์
์ด๋ ํญ๋ชฉ๋ ๋ฐฉ์ถํ์ง ์๊ณ , ์๋ฌ๋ ์๋ฃ๋ง ๊ฐ๋ฅํ Observable์ ๋ณํ์
๋๋ค.
๋ฐ๋ผ์, ์ผ์ด ์ ๋๋ก ๋๋์ง๋ง ๊ฒํ ํ ๋ ์ฌ์ฉํ๊ธฐ ์ข์ต๋๋ค.
Completable๋ Single, Maybe์ ๋ค๋ฅด๊ฒ Observable์ Completable๋ก ๋ฐ๊ฟ ์ ์์ต๋๋ค.
func cacheLocally() -> Completable {
return Completable.create { completable in
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create {}
}
completable(.completed)
return Disposables.create {}
}
}
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print("Completed with no error")
case .error(let error):
print("Completed with an error: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
Swift
๋ณต์ฌ
Maybe
Single๊ณผ Completable์ ์์ด ๋์ ํํ
Maybe๋ .success(value), .completed, .error๋ฅผ ๋ฐฉ์ถํฉ๋๋ค. ํ๋ก์ธ์ค์ ์ฑ๊ณต, ์คํจ ์ฌ๋ถ์ ๋๋ถ์ด ์ถ๋ ฅ๋ ๊ฐ๋ ๋ฐฉ์ถํ ์ ์์ต๋๋ค.
func generateString() -> Maybe<String> {
return Maybe<String>.create { maybe in
maybe(.success("RxSwift"))
maybe(.completed)
maybe(.error(error))
return Disposables.create {}
}
}
generateString()
.subscribe(onSuccess: { element in
print("Completed with element \(element)")
}, onError: { error in
print("Completed with an error \(error.localizedDescription)")
}, onCompleted: {
print("Completed with no element")
})
.disposed(by: disposeBag)
Swift
๋ณต์ฌ