Published on

๐ŸŽ Swift - RxSwift

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

์˜ค๋Š˜์€ ๋ชจ๋‘๊ฐ€ ์“ฐ์ง€๋งŒ ์ €๋งŒ ์•ˆ์“ฐ๊ณ  ์žˆ๋˜ RxSwift๊ฐ€ ๋ฌด์—‡์ด๊ณ , ์–ด๋–ป๊ฒŒ ์“ฐ๋Š”๊ฑด์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์šฐ์„  RxSwift๋Š” Swift๋งŒ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์›๋ž˜์˜ ํ”„๋กœ์ ํŠธ ๋ช…์€ ReactiveX์ด๊ณ  ์ด ์ด๋ฆ„์œผ๋กœ๋Š” ์–ด๋””์„ ๊ฐ€ ๋“ค์–ด๋ดค๋˜ ๊ฒƒ ๊ฐ™๊ธฐ๋„ ํ•˜์ฃ . RxSwift๋Š” ์ด ReactiveX๋ฅผ Swift์— ์ด์‹ํ•œ ๋ฒ„์ „์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.

Reactive X

๊ทธ๋ ‡๋‹ค๋ฉด ReactiveX๋Š” ๋ฌด์—‡์ผ๊นŒ์š”?

๋ณดํ†ต ์šฐ๋ฆฌ๊ฐ€ ์งœ๋Š” ์ฝ”๋“œ๋Š” ๋™๊ธฐ์ (Synchronous) ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ ์€ ์œ— ์ค„๋ถ€ํ„ฐ ์•„๋ž˜ ์ค„๊นŒ์ง€ ์ˆœ์„œ๋Œ€๋กœ ํ•œ ๋ฒˆ์— ํ•œ ์ค„์”ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ์‹คํ–‰๋˜์ฃ . ํ•˜์ง€๋งŒ ReactiveX์—์„œ๋Š” ์ž‘์—…๋“ค์„ ์šฐ์„  ๋ณ‘๋ ฌ(parallel)์ ์œผ๋กœ ์‹คํ–‰ ํ•˜๊ณ , โ€observersโ€ ๋ผ๋Š” ๋†ˆ๋“ค๋กœ ๋‚˜์ค‘์— ๊ฒฐ๊ณผ๋ฌผ์„ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.

ReactiveX์—์„œ ์ž‘์—…์€ ์•„๋ž˜ ๊ตฌ์กฐ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

Observable โฌ…๏ธ Subscribe โ—€๏ธ Observer

Observable์—์„œ ์–ด๋–ค ํ–‰๋™์ด ํฌ์ฐฉ๋˜๋ฉด Observer๋Š” ๊ทธ์— ๋งž๋Š” ๋ฐ˜์‘์„ ๋ณด๋‚ด์ค๋‹ˆ๋‹ค.

Observable

Observable ๋ฐœํ–‰

๋น„๋™๊ธฐ์„ฑ(Asynchronous) ๋ชจ๋ธ์˜ ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๋น„๋™๊ธฐ์„ฑ ํ˜ธ์ถœ์˜ return ๊ฐ’์„ ํ™œ์šฉํ•˜์—ฌ method๋ฅผ ์ •์˜ํ•œ๋‹ค: Observer์˜ ์ผ๋ถ€๋ถ„
  2. ๋น„๋™๊ธฐ์„ฑ ํ˜ธ์ถœ์„ ์ •์˜ํ•œ๋‹ค: Observable
  3. Observer๋ฅผ Observable์— ์—ฐ๊ฒฐํ•œ๋‹ค: Subscribe
  4. return value์™€ value๋ฅผ ํ™œ์šฉํ•œ๋‹ค.
def myOnNext = { it -> do something useful with it };
def myObservable = someObservable(itsParameters);
myObservable.subscribe(myOnNext);

onNext, onCompleted, onError

Subscribe๋œ Observer๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ subset์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

onNext

Observable์ด ์–ด๋–ค ์ •๋ณด๋ฅผ ๋‚ด๋ณด๋‚ด๋ฉด, onNext method์˜ ์ธ์ž๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.

onError

์˜ˆ์ƒํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‹คํŒจํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด onError method๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. onError method๋ฅผ ํ˜ธ์ถœํ•œ ์ดํ›„์—๋Š” onNext, onCompleted method๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. onError method์˜ ์ธ์ž๋กœ๋Š” ๋ฌด์—‡์ด ์—๋Ÿฌ๋ฅผ ์ผ์œผ์ผฐ๋Š”์ง€ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.

onCompleted

์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์„ ๋•Œ, ๋งˆ์ง€๋ง‰ onNext method๋ฅผ ํ˜ธ์ถœํ•œ ํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ •๋ฆฌํ•ด๋ณด๋ฉด, onNext๋Š” ์—ฌ๋Ÿฌ๋ฒˆ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ onError์™€ onCompleted๋Š” ์ข‹๋“  ์‹ซ๋“  ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค!

def myOnNext     = { item -> /* do something userful with item */ };
def myError      = { throwable -> /* react sensibly to a failed call */ };
def myComplete   = { /* clean up after the final response */ };
def myObservable = someMethod(itsParameters);
myObservable.subscribe(myOnNext, myError, myComplete);

Unsubscribing

Observable์ด ๋” ์ด์ƒ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํ•„์š” ์—†์–ด์งˆ ๊ฒฝ์šฐ, subscribe๋ฅผ ํ•ด์ œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Observable์ด unsubscribe ๋˜๋ฉด, ์ƒˆ๋กœ์šด ๋ฐฉ์ถœ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ฉˆ์ถœ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ฃผ์˜ํ•  ์ ์€, ์ด ๊ณผ์ •์€ ์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฉ์ถœ๊ฐ’์ด ์กฐ๊ธˆ ๋” ์ƒ์„ฑ๋œ ํ›„์— ๋ฉˆ์ถœ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Hot & Cold Observables

Hot Observable

์ƒ์„ฑ๋จ๊ณผ ๋™์‹œ์— ๋ฐฉ์ถœ๊ฐ’์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. Subscribeํ•˜๋Š” Observer๋Š” ์ค‘๊ฐ„๊ฐ’๋ถ€ํ„ฐ ๊ฐ’์„ ๋ฐ›์•„๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Cold Observable

Subscribe ๋˜๋ฉด, ๊ทธ๋•Œ๋ถ€ํ„ฐ ๋ฐฉ์ถœ๊ฐ’์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— Subscribeํ•˜๋Š” Observer๋Š” ์ƒ์„ฑ๋œ ๋ชจ๋“  ๊ฐ’์„ ๋ฐ›์•„๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Operators

Chaining Operators

๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„์˜ operator๋“ค์€ Observable ์•ˆ์—์„œ ์ž‘๋™ํ•˜๊ณ , Observable์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋กœ๊ฐ€ ์„œ๋กœ์—๊ฒŒ ์—ฐ๊ฒฐ๋˜์–ด ์ผ์ข…์˜ ์ฒด์ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋•Œ Operator๋“ค ๊ฐ„์˜ chain์€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹œํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ๋œ ์ˆœ์„œ์— ๋”ฐ๋ผ ์ˆœ์„œ๋Œ€๋กœ ํ•˜๋‚˜์”ฉ ์‹œํ–‰๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Operators by Category

๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ๊ฐ operator์˜ ๊ธฐ๋Šฅ์„ ์นดํ…Œ๊ณ ๋ฆฌํ™” ํ•˜์—ฌ ๋ถ„๋ฅ˜ํ•ด๋‘์—ˆ๋Š”๋ฐ, ์ด๋ฅผ ํ•œ ๋ฒˆ์”ฉ ์‚ดํŽด๋ณด๋ฉฐ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

Creating Observables

  • create

Observable์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

let source: Observable = Observable.create { observer in
  for i in 1...5 {
    observer.on(.next(i))
  }
  observer.on(.completed)

  return Disposables.create {
    print("disposed")
  }
}
next(1)
next(2)
next(3)
next(4)
next(5)
completed
disposed
  • just

๊ฐ„๋‹จํ•˜๊ฒŒ ์–ด๋–ค ํ•˜๋‚˜์˜ ๊ฐ’์„ Observable๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

let source = Observable.just(1, 2, 3)
let source2 = Observable.just([1,2,3])
1, 2, 3
[1, 2, 3]
  • from

์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ฐ’์ด ์žˆ๊ณ  ๊ฐ๊ฐ์„ Observable๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

let numbers = [1,2,3,4,5]
let source = Observable.from(numbers)
1
2
3
4
5
  • of

์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ฐ’์„ Observable๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

from๊ณผ just์˜ ์ค‘๊ฐ„ ์ •๋„๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋ฐฐ์—ด์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ๋ฐฐ์—ด ๊ทธ๋Œ€๋กœ ๋ฐฉ์ถœํ•˜๊ณ , ์•„๋‹๋•Œ๋Š” ๊ฐ๊ฐ์˜ ๊ฐ’์„ ํ•˜๋‚˜์”ฉ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let source = Observable.of(1, 2, 3)
let source2 = Observable.of([1, 2, 3])
1
2
3
1, 2, 3

Transforming Observables

  • map

๊ฐ๊ฐ์˜ ๊ฐ’์„ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Observable๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

let source = Observable.from([1, 2, 3]).map { $0 * 2 }
2
4
6
  • flatMap

๊ฐ๊ฐ์˜ Observable์ด ๋ฐฉ์ถœํ•œ ๊ฐ’์— ๋Œ€ํ•ด ์ง€์ •ํ•œ ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๋–„, ๊ฐ ํ•จ์ˆ˜๋Š” ์ž์ฒด์ ์œผ๋กœ ๊ฐ’์„ ๋ฐฉ์ถœํ•˜๋Š” Observalbe์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

let observableInt = Observable.of(1, 2, 3)
let observableString = Observable.of("A", "B", "C")

observableInt
  .flatMap { (x: Int) -> Observable<String> in
    return observableString
  }
next(A)
next(B)
next(A)
next(C)
next(B)
next(A)
next(C)
next(B)
next(C)
completed
  • scan
let observable = Observable.of(1, 2, 3).scan(0) { $0 + $1 }
1
3
6

Filtering Observables

  • filter

๊ฒฐ๊ณผ๋ฅผ ์›ํ•˜๋Š” ์กฐ๊ฑด์— ๋งž๊ฒŒ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let observable = Observable.from([1, 2, 3]).filter { $0 > 1 }
2
3
  • first, last

๊ฒฐ๊ณผ ์ค‘ ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ/๋งˆ์ง€๋ง‰์„ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let observable = Observable.from([1, 2, 3]).first
Optional(1)
  • take, takeLast

๊ฒฐ๊ณผ ์ค‘ ๋งˆ์ง€๋ง‰/์ฒ˜์Œ๋ถ€ํ„ฐ n๋ฒˆ์งธ๊นŒ์ง€ ๋ฐฉ์ถœํ• ์ง€ ์ •ํ•ด์„œ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let observable = Observable.from([1, 2, 3]).takeLast(2)
2
3
  • elementAt

n๋ฒˆ์งธ ์ธ๋ฑ์Šค์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let observable = Observable.from([1, 2, 3]).elementAt(2)
3

Combining Observables

  • merge

์—ฌ๋Ÿฌ Observable์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์˜ Observable๋กœ ํ•ฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, ํ•˜๋‚˜์˜ Observable์ด๋ผ๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ํ•ฉ์ณ์ง„ Observable๋„ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

let observableInt = Observable.of(1, 2, 3)
let observableInt2 = Observable.of(4, 5, 6)
let observableMerge = Observable.of(observableInt, observableInt2).merge()
1
2
4
3
5
6
  • zip

๊ฐ Observable์˜ ์ˆœ์„œ๋Œ€๋กœ ์ง์„ ๋งž์ถ”์–ด ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ง์ด ๋งž์ง€ ์•Š๋Š” ๊ฐ’์€ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.

let observableInt = Observable.of(1, 2, 3, 4)
let observableString = Observable.of("A", "B", "C")
let observableZip = Observable.zip(observableInt, observableString) {
  "\($0)" + $1
}
1A
2B
3C
  • combineLatest

๊ฐ Observable์˜ ๋งˆ์ง€๋ง‰ ๊ฐ’๋ผ๋ฆฌ์˜ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

let observableInt = Observable.of(1, 2, 3)
let observableString = Observable.of("A", "B", "C")
let observableCombineLatest = Observable.combineLatest(observableInt, observableString) {
  "\($0)" + $1
}
1A
1B
2B
3B
3C