상세 컨텐츠

본문 제목

scan Operator로 Observable 이전 값 활용하기

iOS/RxSwift

by 쑤야. 2023. 3. 23. 00:01

본문

Zatch 프로젝트 리팩토링 중…

 

검색에서 물품에 대한 카테고리를 선택할 수 있다.

아래 사진처럼 보라색으로 표시되면 카테고리가 선택된 상태, 회색으로 표시되면 카테고리가 선택되지 않은 상태를 나타낸다.

RxSwift를 활용해 아래 부분을 리팩토링 하는 중이었다.

 

 

카테고리를 단순히 선택 값을 바꿔주는 것은 새롭게 Observable 값을 넣어주면 되기 때문에 어렵지 않게 구현할 수 있었다.

다만 이전 값과 비교해 이전 값이 새로운 값과 같은 경우는 카테고리 선택을 취소시켜야 하는 것 때문에 조금 고민을 했다.

 

이전 값과 새로운 값이 같은지 비교하기 위해서는 이전 값에 대한 정보가 필요한 것이다.

하지만 이미 방출된 이전 값을 어떻게 알아내야 하는 것일까 싶었다.

 

RxSwift에서 이와 관련한 기능이 있는지 구글링 해본 결과

scan이라는 operator로 기능 구현이 가능할 것이라 판단했다.


Scan

apply a function to each item emitted by an Observable, sequentially, and emit each successive value

 

 

이 기능을 보고 Swift의 고차함수 reduce 와 유사하다는 생각이 들었다!

 

func scan<A>(_ seed: A, accumulator: @escaping (A, Element) throws -> A) -> Observable<A>

 

  • seed: 초기값 설정
  • accumulator: (A, Element)에서 A는 이전 값, Element는 새로운 결과

 

이제 내가 기능을 구현한 코드를 살펴보도록 하겠다.

 

1. BehaviorSubject 설정

먼저 Observable 값을 전달하는 역할을 하는 BehaviorSubject을 선언했다.

처음 검색 결과 페이지에 오면 카테고리는 선택되어 있지 않기 때문에 nil로 초기값을 설정해줬다.

 

private let myProductCategory = BehaviorSubject<Int?>(value: nil)

 

2. 새로운 카테고리 값 등록 위한 메서드

func setMyProductCategory(id: Int){
    myProductCategory.onNext(id)
}

 

3. 카테고리 값 유효성 검사 및 값 방출

let myProductCategoryConverter = myProductCategory
    .scan((nil, nil)){ $0.1 == $1 ? (nil, nil) : ($0.1, $1) }
    .map{ $0.1 }

let myProductCategory = myProductCategoryConverter
    .asDriver(onErrorJustReturn: nil)

let isMyProductCategorySelect = myProductCategoryConverter
    .map{ $0 == nil ? false : true }
    .asDriver(onErrorJustReturn: false)

 

myProductCategory는 새롭게 카테고리 값이 선택될 경우 그 값들을 먼저 받는 역할을 한다.

하지만 여기서 등록된 값을 그대로 사용하면 안된다.

카테고리가 선택됐는지, 취소됐는지 구분을 위해서 데이터를 먼저 가공해야 한다.

 

myProductCategory에 scan operator를 적용해 myProductCategoryConverter라는 Observable 프로퍼티를 생성했다.

이때 초기값은 값의 직관적인 이해를 위해 튜플로 데이터 타입을 설정했으며, 카테고리 값이 있을 수도 없을 수도 있기 때문에 옵셔널 타입을 고려해 (nil, nil)로 설정했다.

0번은 이전 값, 1번은 새로운 값을 담아 줄 것이다.

scan operator에서 이전 값과 비교를 한 후, 유효성 결과에 따라 UI에 적용할 카테고리 값을 방출하도록 하였다.

 

scan 연산에서 이전 값과 새로운 값이 같은 경우 (nil, nil)로 다시 초기화 되도록 설정했다.

초기화를 해주지 않는다면, 카테고리를 만약 3 > 3 > 3으로 선택한다고 가정했을 때,

3(선택됨) > 3(취소됨) > 3(다시 선택됨)이 되어야 맞지만

이전 값과 여전히 동일한 값이 들어와 3(선택됨) > 3(취소됨) > 3(취소됨) 이 되기 때문이다.

 

 

이후 myProductCategory는 myProductCategoryConverter의 값을 그대로 Driver로 방출하는 역할을 해 카테고리 값 자체로 사용될 것이며, isMyProductCategorySelect는 myProductCategoryConverter가 nil인지 아닌지를 통해 부울 값을 반환해 카테고리 선택 여부 값에 사용되도록 하였다.

 

@ 참고

https://ios-development.tistory.com/819

관련글 더보기