| fun getPagedFavoritePosts(): Flow<PagingData<PostEntity>> = Pager( |
| config = PagingConfig(25), |
| pagingSourceFactory = { |
| GenericPagingSource { afterKey -> |
| redditApi.loadFavoritePosts(getUserName(), afterKey) |
| } |
| } |
| ).flow.transform { pagingData -> emit(pagingData.map { (it.data as PostDto).map() }) } |
우연히 오픈소스 코드를 보다가, 나라면 map
을 썼을 것 같던 구문에 transform
을 사용한 것을 발견했다. transform
? 처음 들어봤는데 이름이 map
과 왠지 비슷한 일을 할 것같아서 찾아보았다.
map
Flow의 각 값을 변환하여 새로운 요소로 매핑하여 Flow를 반환한다.
| val squaredFlow = flowOf(1, 2, 3, 4).map { |
| it * it |
| } |
transform
Flow의 각 값을 변환하는 함수를 적용한다. transform
의 receiver는 FlowCollector
이므로 요소를 변환하거나, 건너 뛸 수도 있다. 또 emit
함수를 여러번 호출하여 여러 요소를 추가할 수도 있다.
| val squaredFlow = flowOf(1, 2, 3, 4).transform { |
| emit(it * it) |
| } |
이 예시는 map에서도 비슷하지만, 예컨대 이런 것도 가능하다는 것이다.
| |
| val squaredOddFlow = flowOf(1, 2, 3, 4).transform { |
| if (it % 2 == 1) { |
| emit(it * it) |
| } |
| } |
메모리 사용량?
그럼 전부 map보다 유연한 transform을 쓰는 것이 좋지않나...생각이 들던 찰나, 메모리 사용량을 살펴보기로 했다. 미묘하지만 똑같이 사용했을 경우 map
이 메모리 사용량이 더 적었다.
| runBlocking { |
| flowOf(1, 2, 3, 4).map { |
| it * it |
| }.collectLatest { |
| printMemory() |
| } |
| } |
| map() |
| Used memory: 6111 KB |
| Used memory: 8161 KB |
| Used memory: 8192 KB |
| Used memory: 8685 KB |
| runBlocking { |
| flowOf(1, 2, 3, 4).transform<Int, Int> { |
| emit(it * it) |
| }.collectLatest { |
| printMemory() |
| } |
| } |
| transform() |
| Used memory: 6111 KB |
| Used memory: 8687 KB |
| Used memory: 8687 KB |
| Used memory: 9178 KB |
하지만 사실은 map도 내부적으론 transform을 사용중이다. 유일한 차이점은 map에서 사용하는 것이 unsafeFlow
라는 건데...
어째서 메모리 사용량의 차이를 부르는지는 잘 모르겠다. 어쨌든 map, filter 한방에 필요하다면 transform
을 사용하자.
| |
| public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value -> |
| return@transform emit(transform(value)) |
| } |
| @PublishedApi |
| internal inline fun <T, R> Flow<T>.unsafeTransform( |
| @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit |
| ): Flow<R> = unsafeFlow { |
| collect { value -> |
| return@collect transform(value) |
| } |
| } |
| |
| |
| public inline fun <T, R> Flow<T>.transform( |
| @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit |
| ): Flow<R> = flow { |
| collect { value -> |
| return@collect transform(value) |
| } |
| } |
참고자료
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html