상세 컨텐츠

본문 제목

BehaviorSubject 개념 및 활용

iOS/RxSwift

by 쑤야. 2023. 3. 18. 22:32

본문

BehaviorSubject

Represents a value that changes over time.

Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications.

 

 

 

생성자

init(value: Element)

Initializes a new instance of the subject that caches its last value and starts with the specified value.

 

생성자 관련 설명을 보면 알 수 있듯이 BehaviorSubject는 PublishSubject와 다르게, 초기값을 가진다.

 

메서드

public func value() throws -> Element

Gets the current value or throws an error.

 

 

value 메서드를 통해 현재의 값을 관찰할 수 있다!! (이전에 Observer에 대해서 현재 값만 얻고 싶은데, 이게 잘 안돼서 엄청 애먹은 적이 있는데.. 너무너무 반가웠다)


이제 BehaviorSubject를 프로젝트에서 어떻게 활용했는지 설명해보도록 하겠다

 

 

내가 BehaviorSubject를 사용한 UI다.

바텀시트로 내가 설정한 동네가 최대 3가지를 보여주며, 선택된 동네의 경우 노란색으로 표시되는 것이다.

 

BehaviorSubject을 사용해야 겠다고 판단한 이유는 아래 2가지이다.

 

  1. 동네 1개가 반드시 현재 동네로 설정 되어있어야 한다. 즉 초기 값을 가지고 있어야 한다.
  2. 현재 선택된 동네가 무엇인지 알 수 있어야 한다.

 

먼저 ViewModel을 살펴보면,

  • currentTownIndex : 현재 내가 선택한 동네를 가리키는 인덱스로, BehaviorSubject를 통해 초기값을 0번 인덱스로 설정했다.
  • myTowns : 나의 동네들을 담을 수 있는 Array 자료형
  • transform(: ) : currentTownIndex 값이 변경될 경우, 인덱스에 해당하는 동네 이름을 방출하는 역할을 한다
class MainViewModel: BaseViewModel{
    
    private var currentTownIndex = BehaviorSubject(value: 0)
    private var myTowns: [String] = ["상현동", "성복동", "풍덕천동"]
    
    struct Input{
    }
    
    struct Output{
        let currentTown: Driver<String>
    }
    
    func transform(_ input: Input) -> Output {

        let currentTown = currentTownIndex
            .map{
                self.myTowns[$0]
            }.asDriver(onErrorJustReturn: "")
        
        return Output(currentTown: currentTown)
    }
}


다음으로 extension을 통해 데이터 관련 정보를 반환하는 메서드들을 추가했다.

  • getCurrentTownIndex() : 현재 동네를 나타내는 인덱스를 반환히는 역할로, BehaviorSubject의 value 메서드를 활용
  • changeCurrentTown() : 유저가 현재 동네를 변경할 경우 새로운 데이터를 추가하는 역할
extension MainViewModel{
    
    func getCurrentTownIndex() -> Int{
        (try? currentTownIndex.value()) ?? -1
    }
    
    func changeCurrentTown(index: Int){
        currentTownIndex.onNext(index)
    }
}

 

MainViewModel을 활용하는 ViewController는 MainViewController와 ChangeLocationSheetViewController가 있다.

 

이번 포스팅은 BehaviorSubject 설명이 목적이기 때문에 BehaviorSubject 를 활용하는 클래스인 ChangeLocationSheetViewController에 대해서만 설명을 하도록 하겠다

 

먼저 ChangeLocationSheetViewController은 메인탭에서 사용되며, MainViewModel 인스턴스를 참조할 수 있도록 했다.

class ChangeLocationSheetViewController: BaseBottomSheetViewController<Int> {
    
    private var viewModel: MainViewModel!

		.
		.
		.
}
extension ChangeLocationSheetViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.myTownCount()
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return tableView.dequeueReusableCell(for: indexPath, cellType: BaseBottomSheetTableViewCell.self).then{ cell in
            cell.setTitle(viewModel.getTownName(by: indexPath.row))
            if(indexPath.row == viewModel.getCurrentTownIndex()){
                cell.setSelectState()
            }
        }
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        viewModel.changeCurrentTown(index: indexPath.row)
        self.dismiss(animated: true)
    }
}

 

내 동네들 중에서 현재 선택된 한 개의 동네에 대해서만 선택 표시가 되어야 한다.

이때 현재 선택된 동네에 대한 정보를 얻기 위해 MainViewModel의 getCurrentTownIndex() 메서드를 활용했다.

 

또 tableView를 세팅할 때 viewModel의 myTownCount() 메서드, getTownName() 메서드 등을 활용하였다.

 

현재 동네를 변경할 경우는 changeCurrentTown() 메서드를 통해 인덱스를 넘겨주도록 구현했다. 인덱스 값을 넘겨주면 changeCurrentTown 함수 내에서 Observer에 값이 추가되게 되는 것이다.

 

 

위에 현재 동네를 나타내는 Label 값도 맞게 변경된 것을 확인할 수 있다!

관련글 더보기