
import Foundation
import RxSwift
import RxCocoa
class SettingsModel {
static let shared = SettingsModel()
private let disposeBag = DisposeBag()
let themeModeRelay = BehaviorRelay<ThemeMode>(value: UserDefaults.standard.themeMode)
let temperatureUnitRelay = BehaviorRelay<TemperatureUnit>(value: UserDefaults.standard.temperatureUnit)
let petTypeRelay = BehaviorRelay<PetType>(value: UserDefaults.standard.petType)
private init() {
// Relay의 값이 변경될 때마다 UserDefaults에 저장
themeModeRelay
.subscribe(onNext: { mode in
UserDefaults.standard.themeMode = mode
})
.disposed(by: disposeBag)
temperatureUnitRelay
.subscribe(onNext: { unit in
UserDefaults.standard.temperatureUnit = unit
})
.disposed(by: disposeBag)
petTypeRelay
.subscribe(onNext: { type in
UserDefaults.standard.petType = type
})
.disposed(by: disposeBag)
}
}
이런식으로 작성한 Model 클래스가 있다.
themeModeRelay, temperatureUnitRelay, petTypeRelay은 View에서 발생한 이벤트를 ViewModel을 거쳐 UserDefaults에 저장하는 역할도 하고,
반대로 값을 방출해 이를 구독한 ViewModel에서 output을 만드는데 기여하기도 하였다.
동작에는 문제가 없지만, Model이 Model 외의 역할까지 할 수 있는 가능성이 생겨벼린다. 그래서 Relay들에 private 접근제한자를 붙여 은닉화, 캡슐화 하기로 하였다.
이렇게 하면 Model의 Relay들은 값을 구독도 하고 방출도 하겠지만 방출한 값은 외부에서 읽을 수 없게 될 것이다.
따라서 외부의 값 변화를 구독해 UserDefaults를 관리하는 역할만 줄 수 있게 된다.
다만 Model에서 업데이트 된 값을 ViewModel이 받아보긴 해야하니 private 프로퍼티에 접근하고자 할 때 클래스에 get메서드를 작성했던 것처럼, Observable 제공을 하려고 한다.
class SettingsModel {
static let shared = SettingsModel()
private let disposeBag = DisposeBag()
private let themeModeRelay = BehaviorRelay<ThemeMode>(value: UserDefaults.standard.themeMode)
private let temperatureUnitRelay = BehaviorRelay<TemperatureUnit>(value: UserDefaults.standard.temperatureUnit)
private let petTypeRelay = BehaviorRelay<PetType>(value: UserDefaults.standard.petType)
private init() {
// Relay의 값이 변경될 때마다 UserDefaults에 저장
themeModeRelay
.subscribe(onNext: { mode in
UserDefaults.standard.themeMode = mode
})
.disposed(by: disposeBag)
temperatureUnitRelay
.subscribe(onNext: { unit in
UserDefaults.standard.temperatureUnit = unit
})
.disposed(by: disposeBag)
petTypeRelay
.subscribe(onNext: { type in
UserDefaults.standard.petType = type
})
.disposed(by: disposeBag)
}
// Observable로 접근 제공
var themeModeObservable: Observable<ThemeMode> {
return themeModeRelay.asObservable()
}
var temperatureObservable: Observable<TemperatureUnit> {
return temperatureUnitRelay.asObservable()
}
var petTypeObservable: Observable<PetType> {
return petTypeRelay.asObservable()
}
이런 식으로, Relay를 private 접근제한자를 붙여주었고,
var themeModeObservable: Observable<ThemeMode> {
return themeModeRelay.asObservable()
}
이런 형태로 외부에서 접근 가능한 Observable을 제공하게 만들었다.
// ViewModel 클래스
func transform(_ input: Input) -> Output {
// Input 처리
input.toggleMode
.bind(to: settingsModel.themeModeObservable)
.disposed(by: disposeBag)
input.tapTemperature
.bind(to: settingsModel.temperatureObservable)
.disposed(by: disposeBag)
input.tapPetType
.bind(to: settingsModel.petTypeObservable)
.disposed(by: disposeBag)
// output 생성
let themeMode = settingsModel.themeModeObservable
.asDriver(onErrorJustReturn: .light) // 에러 발생 시 기본값 반환
let temperatureUnit = settingsModel.temperatureObservable
.asDriver(onErrorJustReturn: .celsius)
let petType = settingsModel.petTypeObservable
.asDriver(onErrorJustReturn: .dog)
return Output(themeMode: themeMode, temperatureUnit: temperatureUnit, petType: petType)
}
뷰모델의 transform 메서드의 input과 output에서 접근할 때는 위와 같이 Observable을 통해 접근하도록 하였다.

bind(to:) 메서드는 Observer 프로토콜을 준수하는 객체에만 사용할 수 있다. Observable은 값을 방출(emit)할 수 있지만, Observer가 아니기 때문에 값을 받을 수는 없기 때문에 발생한 에러인 것이다.
이전에는 Observer와 Observable 두 가지 역할을 모두 수행할 수 있는 Relay로 직접 접근했었기 때문에 에러가 없었던 것이다.
이를 해결하려면 Model에서 Relay에 접근해서 값을 업데이트 할 수 있도록
var themeModeRelay: BehaviorRelay<ThemeMode> {
return themeModeSubject
}
이런 BehaviorRelay를 추가해주거나,
func setThemeMode(_ mode: ThemeMode) {
themeModeRelay.accept(mode)
}
이런 식으로 접근 가능한 메서드를 추가해 주어야 한다.
Model에 BehaviorRelay를 추가하거나 set메서드를 추가해주는 건 어려운 일이 아니지만,
이러면 처음 생각과 달리 Relay에 읽기와 쓰기까지 가능하게 하는 거라 private을 붙여준 의미가 없는 느낌이다..