Observable Lifecycle
- Create 연산자를 통해 Observable을 생성함(보통 구현되어 있는 Operator를 사용함)
- Subscribe가 되면 Observable이 실행됨
- next를 통해 이벤트(제스쳐, 인스턴스 등)을 Emit함.
- Lifecycle중 오류가 발생하면 error 이벤트를, 정상적으로 모든 이벤트가 완료된다면 completed를 Notification합니다.
- deinit되는 시점, Sequence가 종료되는 시점에 Dispose가 됩니다.
- error 또는 completed를 전달 받으면 Sequence가 종료됨 -> 종료된 이후에는 더 이상 이벤트가 발생하지 않습니다. 즉 Observable에 대한 재사용은 불가능함.
Observable & Observer
- 데이터를 변경해줄 수 있는 이벤트가 있고, 이벤트에 따라서 변경되는 뷰, 로직등이 있습니다.
- 즉 이벤트를 Emit(방출)하는 Observable이 있고, 이벤트를 처리하는 Observer가 있습니다.
- Observable과 Observer를 통해 Stream(데이터의 흐름)을 통제할 수 있고, Operator를 통해 Stream을 변경, 조작할 수 있습니다.
- 하지만 Observable은 Subscrie를 하지 못하기 때문에 이벤트 방출만 할 수 있고, 이벤트에 대한 처리는 하지 못합니다.
- Observer 역시 받은 이벤트를 다른 Observer에게 전달하지 못합니다.
- 그래서 Observer, Observable 두개의 역할을 수행할 수 있는 Subject가 등장하게 됩니다.
Observable Example
let itemsA = [3.3, 4.0, 5.0, 2.0, 3.6, 4.8]
let itemsB = [2.3, 2.0, 1.3]
let disposeBag = DisposeBag()
- Just
- Observable 타입 프로토콜의 타입 메서드로 선언이 되어 있음
- element Parameter로 하나의 값을 받아 Observable을 Return함. 즉 하나의 값만 Emit함
Observable.just(itemsA)
.subscribe { value in
print("just - \(value)")
} onError: { error in
print("just - \(error)")
} onCompleted: {
print("just completed")
} onDisposed: {
print("just disposed")
}
.disposed(by: disposeBag)
- 반환값
- Of
- Observable 타입 프로토콜의 타입 메서드로 선언이 되어 있음
- element Parameter가 가변 파라미터로 선언되어 있어 여러 가지의 값을 동시에 전달할 수 있음.
- 즉 두개 이상의 값을 emit할 수 있음.
Observable.of(itemsA, itemsB)
.subscribe { value in
print("of - \(value)")
} onError: { error in
print("of - \(error)")
} onCompleted: {
print("of completed")
} onDisposed: {
print("of disposed")
}
.disposed(by: disposeBag)
- 반환값
- From
- 배열의 각 요소를 Emit하고 싶다면 from을 사용함
- array Parameter로 배열을 받고, 배열의 각 element를 Observable로 리턴하고 있음
Observable.from(itemsA)
.subscribe { value in
print("from - \(value)")
} onError: { error in
print("from - \(error)")
} onCompleted: {
print("from completed")
} onDisposed: {
print("from disposed")
}
.disposed(by: disposeBag)
- 반환값
- combineLatest
- 2개 이상의 항목을 결합하여 Observable로 Emit함.
- 하나의 항목에만 이벤트가 발생하더라도 이벤트를 발생시킴
- take
- 방출된 아이템 중 처음 n개의 아이템을 내보냄.
// repeatElement : 반복
Observable.repeatElement("MosonLEE")
.take(5) // 최대 5번까지 시도
.subscribe { value in
print("repeat - \(value)")
} onError: { error in
print("repeat - \(error)")
} onCompleted: {
print("repeat completed")
} onDisposed: {
print("repeat disposed")
}
.disposed(by: disposeBag)
- 반환값
Observable vs Subject
- Observable
- Observable은 이벤트를 생성하고 전달합니다.
- 새로운 값을 Observable에 추가를 할 수 없습니다.
- 사용자의 입력 등에 따라 실시간으로 데이터의 변화를 감지하여 표현하고 변경된 데이트를 Subscribe를 통해 emit할 수 있는 객체가 필요합니다.
- Subject
- Subject은 Observable과 Observer를 모두 담당하고 있기 때문에, 이벤트를 전달할 수도 있고 전달받은 이벤트에 대해서 처리할 수도 있습니다.
- 또한 Observable을 여러번 Subscribe 할 경우 독립적인 실행을 갖지만, Subject의 경우 여러 번 Subscribe 하더라도 Observable의 실행이 내부적으로 공유됩니다.
- Subject에는 4개의 종류가 있습니다.
- PublishSubject
- BehaviorSubject
- ReplaySubject
- AsyncSubject
Subject Example
- PublishSubject
- 초기값이 없는 빈 상태로 시작합니다.
- subscribe 이후 시점부터 emit 되는 이벤트를 처리할 수 있는 특성을 가지고 있습니다.
- subscribe 이전에 emit된 이벤트들은 무시됩니다.
//publish
// observable, observer의 역할을 동시에 수행
// subscribe 전/Error/ completed notification 이후 이벤트 무시
// subscribe 후에 대한 이벤트는 다 처리
let publish = PublishSubject<Int>() //초기값이 없는 빈 상태
publish.onNext(1)
publish.onNext(2)
publish
.subscribe {value in
print("publish - \(value)")
} onError: { error in
print("publish - \(error)")
} onCompleted: {
print("publish completed")
} onDisposed: {
print("publish disposed")
}
.disposed(by: disposeBag)
publish.onNext(10)
publish.onNext(11)
publish.on(.next(12))
publish.onCompleted()
//실행 안됨
publish.onNext(15)
publish.onNext(16)
- 반환값
- BehaviorSubject
- BehaviorSubject를 생성할 때 초기값은 필수이다. (PublishSubject와의 차이점)
- Subscribe 이전에 emit한 이벤트가 있다면, 가장 최근에 전달된 이벤트 하나를 전달받을 수 있습니다.
- Subscribe 이전에 emit한 이벤트가 없다면 초기값을 전달합니다.
- 뷰를 미리 채워두기 용이합니다.
behaivor.onNext(1)
behaivor.onNext(2)
//구독하기 전 가장 최신의 값을 가져옴
behaivor
.subscribe {value in
print("behaivor - \(value)")
} onError: { error in
print("behaivor - \(error)")
} onCompleted: {
print("behaivor completed")
} onDisposed: {
print("behaivor disposed")
}
.disposed(by: disposeBag)
behaivor.onNext(10)
behaivor.onNext(11)
behaivor.on(.next(12))
behaivor.onCompleted()
//실행 안됨
behaivor.onNext(15)
behaivor.onNext(16)
- 반환값
- ReplaySubject
- bufferSize에 작성이 된 이벤트만큼, 메모리에 이벤트를 가지고 있다가 subsribe를 한 직후에 한번에 이벤트를 전달합니다.
- 많은 양을 버퍼로 가지고 있을 때는 메모리 부하가 발생할 수 있습니다.
//BufferSize 메모리, array, 이미지
replay.onNext(1)
replay.onNext(2)
replay.onNext(100)
replay
.subscribe {value in
print("replay - \(value)")
} onError: { error in
print("replay - \(error)")
} onCompleted: {
print("replay completed")
} onDisposed: {
print("replay disposed")
}
.disposed(by: disposeBag)
replay.onNext(10)
replay.onNext(11)
replay.on(.next(12))
replay.onCompleted()
//실행 안됨
replay.onNext(15)
replay.onNext(16)
- 반환값
- AsyncSubject
- PublishSubject, BehaviorSubject, ReplaySubjects는 subscribe한 이후 이벤트가 전달되면 즉시 이벤트를 전달합니다.
- 하지만 AsyncSubject는 subscribe을 했더라도 completed 이벤트가 전달이 되기 전까지 어떤 이벤트도 전달하지 않습니다.
- 만약 completed 이벤트가 전달되었다면, 가장 최근 시점에 전달된 next 이벤트 하나를 함께 전달합니다.
//마지막 하나만 넣어줌
async.onNext(1)
async.onNext(2)
async.onNext(100)
async
.subscribe {value in
print("async - \(value)")
} onError: { error in
print("async - \(error)")
} onCompleted: {
print("async completed")
} onDisposed: {
print("async disposed")
}
.disposed(by: disposeBag)
async.onNext(10)
async.onNext(11)
async.on(.next(12))
async.onCompleted()
//실행 안됨
async.onNext(15)
async.onNext(16)
- 반환값
Relay
- Relay는 PublishRelay, BehaviorRelay 2가지가 있고 Subject와 거의 유사한 특성을 가지고 있습니다.
- Subject와 Relay의 가장 큰 차이는 Relay의 경우 Completed Error 이벤트를 받지 못한다는 것입니다.
- UI가 Error와 Completed 이벤트를 받게 된다면 더 이상 next 이벤트를 전달 받을 수 없게 되고 자연스럽게 반응형의 장점이 사라지기 때문입니다.
- Relay는 Completed Error 이벤트를 받지 못하기에 disposed 되기 전까지 Subscribe가 해제되지 않습니다.
- completed or error를 받고 나면 disposed 상태가 되는데 받지 못하니 disposed가 호출되는 deinit시점에 직접 처리를 해주어야함
- 그래서 Relay는 next 이벤트만 처리합니다.
- next 이벤트를 aceept이라는 키워드로 사용합니다.
- 요약해보자면 subject는 Observable, relay는 driver와 자주 사용이됩니다.
Subscribe -> Bind -> Drive
- Observable을 subscribe합니다.
- Background Schedular에서 동작할 수 있는 가능성(네트워크 통신, 파일 다운로드 등)이 있기 때문에 Observable의 데이터 흐름을 MainSchedular(메인 쓰레드)에서 동작할 수 있도록 변경해줍니다.
// 네트워크 통신이나 파일 다운로드 등 백그라운드 작업
button.rx.tap
.observe(on: MainScheduler.instance) //다른 쓰레드로 동작하게 변경
.withUnretained(self)
.subscribe { (vc, _) in
vc.label.text = "안녕 반가워"
}
.disposed(by: disposeBag)
- subscribe와 유사하지만 MainSchedular의 동작과 error 이벤트를 방출하지 않는 특성을 발ㅇ휘할 수 있는 bind를 통해 값을 주입해줍니다.
// bind: subsribe, mainschedular error X
// 무조건 메인 쓰레드에서 작동
button.rx.tap
.withUnretained(self)
.bind { (vc, _) in
vc.label.text = "안녕 반가워"
}
.disposed(by: disposeBag)
- tap의 ControlEvent<Void>를 map Operator를 통해 데이터의 흐름을 조작해줍니다.
- 그럼 String 타입으로 변경이 가능해져서 label.rx.text로 간단하게 bind를 해줄 수 있습니다.
// operator로 데이터의 stream 조작
button.rx.tap
.map{ "안녕 반가워"}
.bind(to: label.rx.text)
.disposed(by: disposeBag)
- Stream이 공유될 수 있는 특성인 Driver Traits를 활용합니다.
// driver traits: bind + stream 공유(리소스 낭비 방지, share())
// Relay와 driver는 연관됐음
button.rx.tap
.map { "안녕 반가워" }
.asDriver(onErrorJustReturn: "")
.drive(label.rx.text)
.disposed(by: disposeBag)
Traits & Driver
- UI 처리에 특화된 Observable을 Trait이라 부르고, RxSwift에서 제공해주는 Observable, RxCocoa에서 제공해주는 Observable이 있습니다.
- Traits에 해당하는 Observable의 공통적인 특성은 Main Thread에서 실행된다는 점입니다.
- 또한 Error 이벤트도 없습니다.
- Traits 중 Signal을 제외하고는 share 연산자가 내부적으로 사용되고 있기 때문에 Subscribe를 하게 될 경우 동일한 sequence를 공유하게 됩니다.
- Rxswift
- Single
- Completable
- Maybe
- RxCocoa
- Driver
- Signal
- ControlProperty/ControlEvent
- Driver
- Main Thread에서의 실행을 보장해줍니다.
- Observable이 UI로 특화된 형태이기 때문에 subscribe만 할 수 있고 값을 변경할 수는 없습니다.
- bind와 달리 Stream 공유가 됩니다.
- bind는 subscribe의 별칭입니다.
- driver는 내부적으로 share(replay: 1, scope: .wholeConnected)가 구현되어 있습니다.
- Share
- 일반적으로 subscribe를 할 때마다 새로운 시퀀스가 생성이 됩니다. 즉 하나의 Observable을 subscribe(bind)하는 곳이 여러 곳이라면 그만큼 호출이 되면서 스트림이 생기게됩니다.
- 따라서 여러 subscribe를 하게 될 경우, 불필요한 리소스가 발생할 수 있기 때문에 내 부적으로 모든 subscribe가 한의 subscribe를 공유할 수 있도록 해야합니다.
- replay와 scope를 통해 버퍼 사이즈와 유지 상태를 결정할 수 있습니다.
- share()가 구현이 되어 있지 않다면 네트워크 요청이 여러번 일어나게 됩니다.
- 따라서 불필요한 콜이나 리소스 낭비가 생기지 않도록 subscribe를 공유할 수 있게 처리해야합니다.
Scheduler
- iOS의 GCD와 유사하며, Thread를 관리를 하기 위해 Scheduler를 사용합니다.
- Serial Scheduler, Concurrent Scheduler, Custom Schedular 등이 있습니다.
- Scheduler의 연산자는 observeOn과 subscribeOn이 있습니다.
- observeOn
- Observable이 Observer에게 다른 Thread로 동작하게 변경합니다.
- observeOn 이후에 오는 연산자에 대한 Scheduler를 지정하게 됩니다.
- observeOn은 특정 작업이나 연산에 대한 scheduler를 변경할 수 있어 여러 번 사용할 수 있습니다.
- ex) observe(on: MainScheduler.instance) - Main Thread로 변경
- subscribeOn
- Observable이 시작이 되는 Scheduler를 지정합니다.
- subscribeOn은 Observable이 동작하는 Scheduler를 변경하는 것이기 때문에 한번만 사용하는 것이 좋습니다.
- observeOn
- subsribe를 사용할 경우 네트워크 통신 작업 등이 발생하면 Background Scheduler 상태로 동작할 수 있습니다.
- UI와 관련된 코드가 들어가 있을 경우 오류가 발생합니다.
Diposable
- Disposable은 Subscribe중인 Stream을 원하는 시기에 처리할 수 있도록 도와줌.
- Observable은 모두 Disposable을 Return함. 이를 통해 Stream을 종료하고 실행되던 Sequence를 모두 종료함.
- Observable의 next 이벤트에 대한 emit이 끝나면, completed -> disposed로 Sequence가 정상적으로 종료됨.
- 하지만 next 이벤트가 무한히 emit될 수 있는 상황에서는 dispose가 되지 않음
- 클래스이 메모리가 해제되는 시점(deinit이 되는 시점)에 Disposable이 되지만 RootVC일 경우에는 메모리에서 클래스가 해제되지 않습니다.
- 따라서 끝까지 이벤트를 진행하고 Disposed 되거나 Disposed 되지 않을 수도 있습니다.
- 이런 케이스에선 직접 관리를 해주어야 하며, 원하는 시점에 dispose 메서드를 호출해서 리소스를 정리해줄 수 있습니다.
- 하지만 여러개를 구독하고 있다면, 일일 이 모든 구독을 종료하는 것은 힘들기 때문에 DisposeBag의 인스턴스를 초기화하는 과정을 통해 한번에 리소스를 정리할 수 있습니다.
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
//intervalObservable.dispose() // 5초 후에 dispose
self.disposeBag = DisposeBag()
}
'IS' 카테고리의 다른 글
iOS Swift 카카오 소셜 로그인(Kakao Social Login) (0) | 2023.02.13 |
---|---|
iOS Swift - Service Level Project(SLP) 회고 (0) | 2023.02.13 |
iOS Collection View APIs (1) | 2022.10.18 |
iOS Remote notification(원격 푸쉬 알림) (1) | 2022.10.18 |
iOS 다국어 대응 하는 법(Localization, 현지화) (1) | 2022.10.18 |