subject 는 ObservableType Protocol을 채택 하고 있는 Observable을 상속하고 있으며,
ObserverType Protocol 을 채택하고 있습니다.
Run Time 시에 Observable 에 값을 추가하여 방출(Emit)이 발생하게 하는 대리자 입니다.
종합적으로 보면 Observable 과 Observer 의 기능들을 수행합니다.
하지만 Subject 는 구독자(subscriber) 가 아니란것을 주의 해야 합니다.
위 이미지 에서 볼수 있듯이
Observable 은 하나의 Observer 만 subscribe 할수 있습니다.
하지만 Subject 는 multicast 방식 이기 때문에 여러개의 Observer 가 subscribe 할수 있습니다.
여기서 Observable 에서 다시 한번 생각 해보면 Observable 은 단지 하나의 Method 라고 생각할수 있기에 어떤 상태도 가지지 않습니다.
그렇기에 새로운 Observer 가 Observable 을 관찰 가능하게 create 해 주어야 합니다.
RxSwift 의
Observable 은 subscribe 된 observer 가 Observable 에 대해 독립적인 실행을 갖습니다.
let observable = Observable<Int>.create{ observer in
observer.onNext(Int.random(in: 0 ..< 100))
return Disposables.create()
}
observable.subscribe(onNext: { (element) in
print("observer 1 : \(element)")
}).disposed(by: disposBag)
observable.subscribe(onNext: { (element) in
print("observer 2 : \(element)")
}).disposed(by: disposBag)
observer 1 : 81
observer 2 : 7
Observable 은 Random 정수를 다른 값으로 방출 합니다.
하나의 Observable 을 subscribe 하는 두개의 Observer 들 이라도 Observable 은 각각 실행됩니다.
이것이 Observable 의 cast 방식인 unicast 방식입니다.
Error 와 Completed 동작
let observable = Observable<Int>.create { observer in
observer.onNext(Int.random(in: 0 ..< 100))
observer.onError(ObservableError.observableError)
return Disposables.create()
}
let firstObservable = observable.subscribe { print("첫번째 Observer === \($0)")}
firstObservable.disposed(by: disposBag)
let secondObservable = observable.subscribe { print("두번째 Observer === \($0)")}
secondObservable.disposed(by: disposBag)
/*
첫번째 Observer === next(60)
첫번째 Observer === error(observableError)
두번째 Observer === next(33)
두번째 Observer === error(observableError)
*/
Error 와 Completed 의 진행은 같습니다.
subject 반응
let behaSub = BehaviorSubject(value: 0)
behaSub.onNext(Int.random(in: 0..<100))
behaSub.subscribe(onNext: { (element) in
print("observer subject 1 : \(element)")
}).disposed(by: disposBag)
behaSub.subscribe(onNext: { (element) in
print("observer subject 2 : \(element)")
}).disposed(by: disposBag)
observer subject 1 : 92
observer subject 2 : 92
print를 보게 되면 subject 에서 subscribe 가 이루어지면 Event 가 전달 되는 것은 같은 값으로 사용됩니다.
그럼 간단하게 Observable 과 Subject 에 대한 차이점에 대해 이야기 할수 있습니다.
Observable 은 하나의 Observer 에 대한 간단한 Observable 이 필요할때
Subject 는 자주 데이터를 저장하고 수정 , 여러개의 Observer 가 데이터를 관찰해야 할때
let pubSub = PublishSubject<Int>()
pubSub.onNext(Int.random(in: 0 ..< 10 ))
pubSub.subscribe(onNext: {
print($0)
}).disposed(by: disposBag)
pubSub.onNext(Int.random(in: 11 ..< 20))
pubSub.subscribe(onNext: {
print($0)
}).disposed(by: disposBag)
pubSub.onNext(Int.random(in: 21 ..< 30))
// print
// subscribe 이전 onNext 는 무시
19 // 첫번째 subscribe
25 // 첫번째 subscribe
25 // 두번째 subscribe
PublishSubject 또한 마찬가지로 반응 합니다.
subscribe 되기전 Event 에 대해선 무시하지만
subscribe 가 이루어 진뒤 일어나는 Event 에 대해선 모든 공유하게 됩니다.
그럼 Subject의 종류들을 살펴보겠습니다.
두번째 줄이 구독 하였을때 1 은 받지 못한다. 하지만 2 와 3 은 받을수 있다.
세번째 줄이 구독 하였을때 1,2 은 받지 못한다. 하지만 3 은 받을수 있다.
PublishSubject 는 구독(subscribe) 되기 전에 Emit(방출) 되는 모든 Event 들을 무시합니다.
그이후 구독(subscribe) 이 이루어진후 Event 만 방출 합니다.
또한 publishSubject 는 empty 의 상태로 시작합니다.
let subject = PublishSubject<String>()
subject.onNext("첫번째 넥스트")
subject.subscribe(onNext : {
print($0)
}).disposed(by: disposeBag)
subject.onNext("두번째 넥스트")
subject.onNext("세번째 넥스트")
//print
두번째 넥스트
세번째 넥스트
앞써 설명하였듯이
PublishSubject 는 Muticast 방식이고 하나의 Subject 로 여러 Observer 와 공유 할수 있습니다.
하지만 onCompleted , onError 가 발생하였을 경우는 어떻게 될지 알아 보겠습니다.
let subject = PublishSubject<String>()
subject.onNext("첫번째 onNext === 1")
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext : === 2")
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("세번째 onNext : === 3")
//print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
subject 의 기본 동작 입니다.
이렇게 subscribe 하는 이후 시점부터 event 를 전달받는
observer 들이 존재합니다 . 여기서 세번째 onNext 이후 completed 가 진행되면
let subject = PublishSubject<String>()
subject.onNext("첫번째 onNext === 1")
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext : === 2")
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("세번째 onNext : === 3")
subject.onCompleted()
//print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === completed
두번째 observer === completed
모든 Observer 는 completed 를 전달 받게 됩니다.
여기에 새로운 observer 를 추가해보겠습니다.
let subject = PublishSubject<String>()
subject.onNext("첫번째 onNext === 1")
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext : === 2")
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("세번째 onNext : === 3")
subject.onCompleted()
let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
thirdObserver.disposed(by: disposBag)
//print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === completed
두번째 observer === completed
세번째 observer === completed
PublishSubject 를 subscribe 하는 observer 는 구독 전 Event 에 대한 값을 받지 못합니다.
하지만 completed 가 일어난뒤 구독하는 observer 들은 더이상의 onNext 를 할수 없기에 completed 를 전달 받습니다.
실제 completed 가 일어난뒤
subject.onNext("네번째 onNext : === 4")
해 보아도 subject는 Event 를 방출하지 않습니다.
그렇다면 Error Event 를 전달하게 된다면 observer 는 어떤 반응을 할지 알아보겠습니다.
enum SubjectError : Error {
case subjectError
}
let subject = PublishSubject<String>()
subject.onNext("첫번째 onNext === 1")
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext : === 2")
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("세번째 onNext : === 3")
//subject.onCompleted()
subject.onError(SubjectError.subjectError)
let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
thirdObserver.disposed(by: disposBag)
subject.onNext("네번째 onNext : === 4")
//print
첫번째 observer === next(두번째 onNext : === 2)
첫번째 observer === next(세번째 onNext : === 3)
두번째 observer === next(세번째 onNext : === 3)
첫번째 observer === error(subjectError)
두번째 observer === error(subjectError)
세번째 observer === error(subjectError)
completed 와 마찬가지고 Error 가 발생되는 시점부터 어떠한 Event 도 받을수 없게 됩니다.
적재적소:
시간의 흐름과 연관된 모델에 적합하다.
쇼핑몰 세일 이벤트 의 경우 11시까지 진행이면 그 전에 App에 log 한 고객들 에겐 배너를 보여줄 필요가 있지만 이후 log 한 고객들에겐 그 배너를 보여줄 필요가 없다.
BehaviorSubject 는 생성시점에 초기값을 구현하고 구독시 최신 이벤트를 저장해두었다가 전달합니다.
PublishSubject와 Subject로서의 기능을 동일합니다. 하지만 초기값을 설정한다는 점과 최신 이벤트를 저장해두고 구독시 전달한다는 점이 다릅니다.
PublishSubject 는 받지 못하지만 BehaviorSubject 은 구독 전 최신 이벤트를 저장해 주기 때문에 구독전 이벤트를 사용할수 있습니다.
let subject = BehaviorSubject<String>(value: "초기값")
// 구독전 Event 없음
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("첫번째 onNext")
// seoncdObserver 는 최신값이 초기값이 아니기때문에 초기값을 받을수 없다.
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext")
//print
첫번째 observer === next(초기값)
첫번째 observer === next(첫번째 onNext)
두번째 observer === next(첫번째 onNext)
첫번째 observer === next(두번째 onNext)
두번째 observer === next(두번째 onNext)
이후 error , completed 의 방출에서의 observer 반응은 publishSubject 와 동일하게 동작합니다.
let subject = BehaviorSubject<String>(value: "초기값")
// 구독전 Event 없음
let firstObserver = subject.subscribe { print("첫번째 observer === \($0)")}
firstObserver.disposed(by: disposBag)
subject.onNext("첫번째 onNext")
// seoncdObserver 는 최신값이 초기값이 아니기때문에 초기값을 받을수 없다.
let secondObserver = subject.subscribe { print("두번째 observer === \($0)")}
secondObserver.disposed(by: disposBag)
subject.onNext("두번째 onNext")
subject.onCompleted()
// completed 이후로는 어떠한 subject 의 emit 은 종료되니 observer 는 어떠한 event도 받지못한다.
let thirdObserver = subject.subscribe { print("세번째 observer === \($0)")}
thirdObserver.disposed(by: disposBag)
subject.onNext("세번째 onNext")
적재적소:
User 가 Profile 을 수정할 경우 BehaviorSubject와 binding 하여 data가 변하면 최신 data로 변경하기 용이 하다.
ReplaySubject 는 방출되었던 최신 값들을 지정한 bufferSize에 맞춰 묶은뒤 방출합니다.
또한 생성자가 아닌 create 메소드를 사용하여 생성합니다.
※ 버퍼 사이즈는 메모리에 할당되므로 버퍼 사이즈가 커질 수록 메모리에 큰 부하를 갖는 것을 주의 해야 합니다.
하지만 구독이 먼저 이루어지고 방출이 시작된다면? bufferSize 만큼 저장하지 않고 바로바로 방출합니다.
let subject = ReplaySubject<Int>.create(bufferSize: 2)
(1...10).forEach { subject.onNext($0) }
subject.subscribe { print($0)}.disposed(by: disposBag)
let first = subject.subscribe { print("첫번째 observer == \($0)")}
first.disposed(by: disposBag)
let second = subject.subscribe { print("두번째 observer == \($0)")}
second.disposed(by: disposBag)
// 구독 이후 onNext 되는 Event 는 buffer 로 묶지 않고 전체 방출한다.
subject.onNext(99)
subject.onNext(999)
subject.onNext(9999)
//print
next(9) // buffer
next(10) // buffer
첫번째 observer == next(9) // buffer
첫번째 observer == next(10) // buffer
두번째 observer == next(9) // buffer
두번째 observer == next(10) // buffer
// 묶지 않고 방출
next(99)
첫번째 observer == next(99)
두번째 observer == next(99)
next(999)
첫번째 observer == next(999)
두번째 observer == next(999)
next(9999)
첫번째 observer == next(9999)
두번째 observer == next(9999)
이 밖에도 기존의 subject 동작은 기본적으로 같습니다.
completed, error 가 방출되면 동일하게 반응 합니다.
적재적소:
방대한 데이터가 아닌 UI 에서 부담없이 볼수 있는 최근검색 부분에 사용하면 좋을것 같다.
최근 검색의 경우 모바일에선 너무 많은 Data를 나타내고 있지 않기에 메모리의 무리도 없다.
AsyncSubject 는 기존 Subject 들과 달리 이벤트를 전달하는 시점에 차이가 있습니다.
다른 Subject 들은 이벤트를 방출 할시 구독자에게 이벤트를 전달합니다.
하지만 AsyncSubject 는 Completed 이벤트가 전달되기 전까지는 어떠한 이벤트도 구독자에게 전달하지 않습니다.
또한 Completed 이벤트가 전달되면 가장 최신 이벤트만을 next 로 Observer 에게 전달합니다.
let subject = AsyncSubject<String>()
let firstObserver = subject.subscribe { print($0) }
firstObserver.disposed(by: disposBag)
subject.onNext("첫번째 넥스트")
subject.onNext("두번째 넥스트")
subject.onNext("세번째 넥스트")
//completed 되지 않았기 때문에 출력문 없음
컴플리트가 되지 않아 Event를 Observer 가 전달받지 못하였습니다. 그렇기에 Observer는 Event를 출력할수 없습니다.
let subject = AsyncSubject<String>()
let firstObserver = subject.subscribe { print($0) }
firstObserver.disposed(by: disposBag)
subject.onNext("첫번째 넥스트")
subject.onNext("두번째 넥스트")
subject.onNext("세번째 넥스트")
subject.onCompleted()
//print
//next(세번째 넥스트)
//completed
하지만 completed Event 가 Emit 되면 next 로 가장 최신의 값 completed 를 Observer 에게 전달합니다.
또한 Error Event 가 방출 되면 최근 next 이벤트 없이 Error Event 만 방출하고 종료됩니다.
Hot Observable 과 Cold Observable 의 차이점 입니다.
Hot -multicasting-
Cold - unicasting-
print("Start")
let observable = Observable<String>.create { observer in
observer.onNext("방출")
return Disposables.create()
}
// .subscribe(onNext: {
// print($0)
// })
print(observable)
let sub = PublishSubject<String>()
sub.onNext("방출")
print(sub)
//print
Start
RxSwift.(unknown context at $111253298).AnonymousObservable<Swift.String>
RxSwift.PublishSubject<Swift.String>
observable 은 생성되지 않았습니다.
하지만 PublishSubject 는 생성되었음을 알수 있습니다.
이유에 대해서 알아보자면
Observable.create 를 이용해 Observable 을 생성하게 되면 return 으로 Observable을 Disposables.create 만들어 반환합니다.
Observable은 Disposable 을 상속 받고있고 있습니다.
여기서 Observable 에게 subscribe 가 이루어지면
Observable 은 생성되어 방출하기 시작합니다.
Start
방출
RxSwift.(unknown context at $10245ac94).BinaryDisposable //BinaryDisposable 로 변함.
RxSwift.PublishSubject<Swift.String>
또한 Hot 은 Mulitcasting cold 는 unicasting 이기 때문에 Hot은 데이터를 구독자 간에 공유가 되지만 cold는 구독이 완료된 후에 생성,방출 하기 때문에 구독자마다 항상 새로운 실행이 이루어져 값을 공유할수 없습니다.
cold 는 값이 안에서 생성
Hot 은 값이 밖에서 생성
let coldObservable = Observable<Int>.create { observer in
observer.onNext(Int.random(in: 0 ..< 100))
return Disposables.create()
}
//cold = 구독 X data 생성 in 방출 X
// --------------
let randomNum = Int.random(in: 0 ..< 100 )
let hotObservable = Observable<Int>.create { observer in
observer.onNext(randomNum)
return Disposables.create()
}
//hot = 데이터를 외부에서 생성한뒤 대입해준다.
coldObservable.subscribe { print("Cold 첫번째 구독 === \($0)") }.disposed(by: disposBag)
coldObservable.subscribe { print("Cold 두번째 구독 === \($0)") }.disposed(by: disposBag)
hotObservable.subscribe { print("Hot 첫번째 구독 === \($0)")}.disposed(by: disposBag)
hotObservable.subscribe { print("Hot 두번째 구독 === \($0)")}.disposed(by: disposBag)
//print
Cold 첫번째 구독 === next(26)
Cold 두번째 구독 === next(23)
Hot 첫번째 구독 === next(51)
Hot 두번째 구독 === next(51)