Swift는 didset을 통해 데이터가 단방향으로 구성되어서 흘러간다.
MVC에서는 데이터가 이렇게 단방향으로 구성되는 것에 뷰와 컨트롤러가 붙어 있기에 didset으로 충분히 처리가 가능해 고려해주지 않아도 됐다.
MVVM은 View와 Model 사이를 ViewModel을 통해서 양방향 바인딩이 가능하다.
클로저 구문을 통해서 바인드 메소드를 사용했다.
RxSwift는 이 양방향 바인딩을 도와주는 연산자가 들어있어 더 쉽게 처리가 가능하다.
RxSwift
항상 옵저버블이 존재한다.
- Observable : 이벤트를 관찰하는 객체 : 사용자가 버튼을 누른다 -> 어떤 일이 일어나?
- Observer : 다양한 로직이 발생하면 처리하는 객체
이 Observer는 처리해야 하는 event가 있고, 이 event를 처리하기 위해서는 무조건 "구독"을 해야 한다.
이 "구독"을 subscribe라고 한다.
그리고 event는 총 3가지 종류가 있다.
1. next event ()
2. error event (notification)
3. complete event (notification)
- Subject : Observable + Observer
- Relay : Subject에서 UI적으로 확대시킨 개념
Share()
이런 식으로 같은 객체가 반복되면 상수로 빼줄 수 있다.
코드를 보면 validation으로 빼줬는데, 이 validation이라는 객체가 2번 실행된다.
그 말은 즉, 메모리에 2번 올라간다는 뜻이다.
Observable과 Observer의 관계는 1:1인데
이렇게
Observable은 현재 validation 1개이고,
Observer는 여러개인 경우에 리소스 낭비를 줄이기 위해서
bind를 통해 이벤트를 전달 받아서 처리할 때는 share() 연산자를 사용할 수 있다.
📌 Observable vs Subject vs Relay 비교하기
1. Observable
Observable과 Observer이 1:1로 대응됨
값이 다 다르게 나오는 걸 확인할 수 있다
// MARK: - Observable
// 옵저버블
let sampleInt = Observable<Int>.create { observer in
observer.onNext(Int.random(in: 1...100))
return Disposables.create()
}
// 옵저버블에 해당하는 옵저버 총 3개 만들었음
sampleInt.subscribe { value in
print("sampleInt:", value)
}
.disposed(by: disposeBag)
sampleInt.subscribe { value in
print("sampleInt:", value)
}
.disposed(by: disposeBag)
sampleInt.subscribe { value in
print("sampleInt:", value)
}
.disposed(by: disposeBag)
sampleInt: next(10)
sampleInt: next(50)
sampleInt: next(40)
2. Subject
1. Observable과 Observer의 역할 동시에 가능
2. Publish, Behavior, Replay, Async
3. Subject는 Stream을 공유한다. (리소스 방지 = 메모리 낭비를 줄이고자!)
4. 어떻게 가능한데? - share()라는 메소드를 이미 갖고 있거든, 그래서 해당 키워드를 써줄 필요 X
// MARK: - Subject
// 초기값 가지는 서브젝트
let subjectInt = BehaviorSubject(value: 0)
subjectInt.onNext(Int.random(in: 1...100))
subjectInt.subscribe { value in
print("subjectInt:", value)
}
.disposed(by: disposeBag)
subjectInt.subscribe { value in
print("subjectInt:", value)
}
.disposed(by: disposeBag)
subjectInt.subscribe { value in
print("subjectInt:", value)
}
.disposed(by: disposeBag)
subjectInt: next(70)
subjectInt: next(70)
subjectInt: next(70)
3. Relay
1. next 이벤트만 방출 Cuz, UI에 최적화된 요소라서 (complete도 방출)
2. Publish, Behavior
3. next 대신 accept 키워드 사용
4. Drive랑 같이 사용 (아래에 설명 나옴)
Traits
UI 처리에 특화되어 있는 옵저버블들 like, Driver/ControlProperty/ControlEvent
대부분 stream을 공유하는 키워드인 share()가 들어있다.
(따로 글 작성)
📌 Subscribe / Bind / Drive
Subscribe
1. next / error / complete 이벤트를 다 구독할 수 있다.
2. UI 업데이트는 Main Thread에서 처리해줘야 하는데 Subscribe를 통해 구독해주면 문제가 생길 수 있기에
GCD 개념을 통해 해당 코드가 메인에서 작동할 수 있게 엮어준다.
ex. DispatchQueue.main.async { ~ }
3. 근데 그대신에 Rx에서 .observer(on: )이라는 연산자를 통해서 처리해줄 수 있다.
4. Stream을 공유하지 않는다.
Bind
1. error 이벤트를 받지 않는다.
2. Main Thread에서만 작동하기 때문에 UI에 최적화되어 있다.
subscribe, bind 둘 다 써도 되지만 bind를 써줄 수 있다면 bind를 쓰자!
subscribe를 쓸 경우에는 error, complete를 지워주면 된다.
Drive
1. Bind의 특성을 다 가지고 있다.
2. error를 뱉지 않는다. complete를 뱉긴 한다.
3. share 사용하지 않더라도 Stream을 공유할 수 있다.
asDriver는 drive를 쓸 수 있는 객체로 변환해준다.
driver라는 아이는 error를 방출하지 않기는 한데, 옵저버블에 시퀀스의 흐름에서 에러가 방출하기도 함
그럴 때 시퀀스가 흐르면서 에러가 있기에 에러에 대한 대응을 해줘야 하니까!
평범한 Observable을 Driver로 바꾸어서 사용 가능
asDriver(onErrorJustReturn:): error 발생 시, error 대신 지정한 기본값을 return
asDriver(onErrorDriverWith:): error를 수동적으로 리턴하여, error에 이벤트를 handle할 수 있음
asDriver(onErrorRecover:): driver에 사용되며 error에 대한 이벤트를 handle할 수 있음
예시
ValidationViewModel
다시 이벤트를 전달받지 않기에 옵저버블로 만들어도 되지만,
UI에 특화된 요소로 만들어 주고 싶고, 초기값이 있는 상태니까, BehaviorRelay로 만들었음
let validText = BehaviorRelay(value: "8자 이상 작성해주세요")
ValidationViewController
뷰모델의 validText를 validationText에 전달하겠다!
Relay의 짝꿍인 Drive를 썼다.
viewModel.validText
.asDriver()
.drive(validationLabel.rx.text)
.disposed(by: disposeBag)
왜 asDriver()이냐면, 초기값이 있는 상태로 끝나는 거니까, 따로 예외처리가 있는 상황이 아니거든…
driver로 바뀌면,, RxCocoa의 Trait으로 바뀌게 된다.
Dispose
Dispose는 리소스 정리이다.
DisposeBag은 쓰레기봉투로 한 방에 정리한 것!
.dispose()는 수동 리소스 정리이다.
UI는 무한 시퀀스인데 이 무한 시퀀스를 유한 시퀀스로 변경 시에는 리소스 해제를 수동으로 해줘야 하고 그럴 때 사용한다.
Rx에서는 데이터의 타입이 stream을 타면서 변경된다.
데이터의 타입이 스트림을 타면서 변경되는 상태, 반응형이라고 하지
button → UIButton
button.rx → stream을 거쳐서 let sample: Reactive<UIButton>
button.rx.tap → let sample: ControlEvent<Void>
debug() 라는 연산자
이걸 통해서 데이터 스트림 중간에 콘솔창에 출력해볼 수 있다.
SubjectViewController 부분 질문
PublishRelay가 drive를 통해서 구독을 하고 난 후에 처음에는 비어있는 상태로 들어오는데
searchBar에서 구독을 할 때 filterData라는 메소드가 들어있는데 해당 부분에서
list.accept(result)를 통해 list에 데이터를 업데이트하면서 list의 데이터가 바뀌는 것이다.
UI 속성이 절대 Error를 방출하지 않는 것이지 Complete를 방출하긴 한다.
무한한 시퀀스를 강제로 dispose 시켜주면 유한한 시퀀스가 되어서 Complete를 방출하게 되는 것이다!
수동 Dispose!!!!
'⭐️ 개발 > Rx' 카테고리의 다른 글
7. Traits - ControlProperty, ControlEvent, Driver (0) | 2023.01.16 |
---|---|
6. Binder (0) | 2023.01.16 |
[Rx] Error Operator (0) | 2023.01.16 |
[Rx] Input/Output 패턴 적용하기 - 비즈니스 로직 분리!! (4) | 2022.11.02 |
[Rx] Observable, Observer, Disposable, Subject, Relay (4) | 2022.10.26 |