[Swift] RxSwift - map, flatMap의 차이점과 용도

호랭이·2022년 6월 8일
2

🍎 RxSwift

목록 보기
6/6

RxSwift를 이용한 연습 프로젝트를 진행하면서
종종 두뇌 CPU 사용량 초과로 뇌정지가 왔는데 😵‍💫
그럴 때 가장 자주 헤맸던 부분이 mapflatMap의 사용이었다.

map

flatMap

이번 프로젝트에서 subcribe전까지의 stream 가공에서 가장 많이 썼던 요것들...
다시는 얘네 앞에서 뇌정지가 오지 않도록 정리를 해보겠다.

map

공식문서:
각 항목에 함수를 적용하여 Observable에서 내보내는 항목을 변환합니다.

즉, Observable이 가진 요소들에 특정 연산을 수행한다.
1, 2, 3을 요소로 가진 Observable에 .map{ $0 * 2 }를 실행하면
2, 4, 6을 요소로 가진 Observable이 되는 것이다.

Observable<Int>
    .of(1, 2, 3)
    .map{ $0 * 2 }

여기에 subscribe를 하면 Int 타입 요소들이 방출된다.

flatMap

공식문서:
Observable에 의해 방출된 항목을 Observable로 변환한 다음 그 방출을 단일 Observable로 평면화합니다.

Observable의 요소들을 각각의 Observable로 만들고, 만들어진 각각의 Observable이 요소들을 방출하게 된다.

🤔❓🤔❓🤔❓🤔❓🤔❓
1, 2, 3을 요소로 가진 Observable에 .flatMap{ $0 * 2 }를 실행하면
2, 4, 6을 각각의 요소로 가진 3개의 Observable이 되며 이 Observable들이 요소를 방출하고, 그것을 다시 하나의 Observable로 방출된다...?

❓🤔❓🤔❓🤔❓🤔❓🤔

...라고 쓰면서 아니 그러면 map하고 다른게 뭐야...? 요소들을 Observable로 나눴다가 다시 하나의 Observable로 만들면 뭐가 다른건데...뭘 한 건데...뭔데...
라는 생각이 들었다...

그래서 조금 더 공부를 해봤더니

💡
flatMap
1. 한번에 여러 스트림을 사용할 수 있다.
2. 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능하다.

라는 장점이 있었다.


observable1의 요소: "A", "B", "C"
observable2의 요소: "a", "b", "c"

이렇게 두개의 스트림이 있고 "Aa", "Ab", "Ac", "Ba"....을 가진 Observable을 얻고 싶을 때 아래와 같은 작업이 가능하다.

observable1
// 1.
.flatMap{ uppercase: String -> Observable<String> in 
	// 2.
	return observable2.map{ uppercase + $0 }
}
  1. observable1의 각 요소 uppercase들을 이용해서 flatMap을 할것이다.
  2. observable2.map을 통해서 observable2의 각 요소에 uppercase를 합친 것을 새로운 요소로 갖는 observable이 리턴된다.
  3. uppercase는 3개이기 때문에 2번 과정이 3번 반복되면서 Observable이 3개가 된다.
  4. flatMap을 썼기 때문에 3개의 Observable이 평면적으로 합쳐진다.

마블 다이어그램으로 나타내면 아래와 같다.

  • 이렇게 uppercase 각각의 Observable이 만들어지고
  • 평면적으로 합쳐진다


flatMap은 하나의 스트림을 가공하는 클로저 내부에서 또 다른 스트림을 사용할 때 편리하다!
subsribe를 하지 않고 observable의 요소들을 이용할 수 있고, 결과를 리턴값으로 사용할 수도 있다. (do를 쓰면 스트림의 요소들을 사용할 수는 있겠지만 리턴값으로 사용은 어렵다.)


만약 flatMap을 쓰지 않고 map만을 써서 시도한다면, observable에 아무리 map을 해봤자 요소에 대한 연산을 마친 observable만 반환되기 때문에 중첩의 늪에 빠지게 될 가능성이 크다.

Observable<Observable<Observable<String>>>같은 끔찍한 리턴 타입을 목격한다던가...(경험담)



연습 프로젝트에서 활용해본 사례로는

let strawBananaJuiceMakingResult = input.strawBananaJuiceButtonDidTap
	.flatMap{ self.juiceMaker.juiceMakingResult(.strawberryBananaJuice) }

위와 같은 코드가 있었다.
input.strawBananaJuiceButtonDidTapObservable<Void>타입이고,
flatMap 클로저 내부의 juiceM akingResult메소드는 Observable<Bool> 타입을 반환한다.

input.strawBananaJuiceButtonDidTap 에서 방출되는 Void 타입 요소를 self.juiceMaker.juiceMakingResult(.strawberryBananaJuice)를 통해 Observable<Bool>로 만들고, 만들어진 이 Observable 또한 요소를 방출한다.
그리고 flatMap을 통해 Observable들을 하나로 평평하게 만들어서 방출된다.
그 결과 Observable<Bool> 타입을 갖게 되었다.

profile
삐약

0개의 댓글